Skip to content
Open
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
8 changes: 7 additions & 1 deletion src/Orleans.CodeGenerator/GeneratedCodeUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using System.Linq;
using Microsoft.CodeAnalysis;
using Orleans.CodeGenerator.SyntaxGeneration;
using static Orleans.CodeGenerator.SyntaxGeneration.SymbolExtensions;
Expand All @@ -11,10 +12,15 @@ internal static class GeneratedCodeUtilities

internal static string GetGeneratedNamespaceName(ITypeSymbol type) => type.GetNamespaceAndNesting() switch
{
{ Length: > 0 } ns => $"{CodeGeneratorName}.{ns}",
{ Length: > 0 } ns => $"{CodeGeneratorName}.{string.Join(".", ns.Split('.').Select(static segment => segment.EscapeIdentifier()))}",
_ => CodeGeneratorName
};

/// <summary>
/// Gets the form of a generated namespace used in metadata names, where identifiers are never escaped.
/// </summary>
internal static string GetMetadataNamespaceName(string generatedNamespace) => generatedNamespace.Replace("@", string.Empty);

internal static uint? GetId(LibraryTypes libraryTypes, ISymbol memberSymbol)
{
return memberSymbol.GetAttribute(libraryTypes.IdAttributeType) is { } attr
Expand Down
4 changes: 2 additions & 2 deletions src/Orleans.CodeGenerator/MetadataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,8 @@ private static GeneratedInvokableMetadata CreateGeneratedInvokableMetadata(

private static string GetGeneratedInvokableMetadataName(string generatedNamespace, string name, int genericArity)
=> genericArity == 0
? $"{generatedNamespace}.{name}"
: $"{generatedNamespace}.{name}`{genericArity}";
? $"{GeneratedCodeUtilities.GetMetadataNamespaceName(generatedNamespace)}.{name}"
: $"{GeneratedCodeUtilities.GetMetadataNamespaceName(generatedNamespace)}.{name}`{genericArity}";

private static string GetGeneratedInvokableClassName(ProxyInterfaceModel proxy, MethodModel method)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ public GeneratedInvokableDescription(
string? returnValueInitializerMethod,
InvokableEmissionPlan emissionPlan)
{
var metadataNamespaceName = GeneratedCodeUtilities.GetMetadataNamespaceName(generatedNamespaceName);
if (methodDescription.AllTypeParameters.Count == 0)
{
MetadataName = $"{generatedNamespaceName}.{generatedClassName}";
MetadataName = $"{metadataNamespaceName}.{generatedClassName}";
}
else
{
MetadataName = $"{generatedNamespaceName}.{generatedClassName}`{methodDescription.AllTypeParameters.Count}";
MetadataName = $"{metadataNamespaceName}.{generatedClassName}`{methodDescription.AllTypeParameters.Count}";
}

BaseType = baseType;
Expand Down
2 changes: 1 addition & 1 deletion src/Orleans.CodeGenerator/Model/InvokableEmissionPlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal sealed record InvokableEmissionPlan(
/// <summary>The metadata name of the generated type within <paramref name="generatedNamespace"/>.</summary>
public string GetMetadataName(string generatedNamespace)
{
var name = string.IsNullOrEmpty(generatedNamespace) ? ClassName : $"{generatedNamespace}.{ClassName}";
var name = string.IsNullOrEmpty(generatedNamespace) ? ClassName : $"{GeneratedCodeUtilities.GetMetadataNamespaceName(generatedNamespace)}.{ClassName}";
return TypeParameters.Count == 0 ? name : $"{name}`{TypeParameters.Count}";
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Orleans.CodeGenerator/Model/ProxyEmissionPlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal sealed record ProxyEmissionPlan(
/// <summary>The metadata name of the generated type within <paramref name="generatedNamespace"/>.</summary>
public string GetMetadataName(string generatedNamespace)
{
var name = string.IsNullOrEmpty(generatedNamespace) ? ClassName : $"{generatedNamespace}.{ClassName}";
var name = string.IsNullOrEmpty(generatedNamespace) ? ClassName : $"{GeneratedCodeUtilities.GetMetadataNamespaceName(generatedNamespace)}.{ClassName}";
return TypeParameters.Count == 0 ? name : $"{name}`{TypeParameters.Count}";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ public ProxyInterfaceDescription(
}
}

GeneratedNamespace = InterfaceType.GetNamespaceAndNesting() switch
{
{ Length: > 0 } ns => $"{GeneratedCodeUtilities.CodeGeneratorName}.{ns}",
_ => GeneratedCodeUtilities.CodeGeneratorName
};
GeneratedNamespace = GeneratedCodeUtilities.GetGeneratedNamespaceName(InterfaceType);

var names = new HashSet<string>(StringComparer.Ordinal);
TypeParameters = new List<(string Name, ITypeParameterSymbol Parameter)>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,7 @@ private INamedTypeSymbol GetEffectiveBaseType()

public string Namespace => Type.GetNamespaceAndNesting();

public string GeneratedNamespace => Namespace switch
{
{ Length: > 0 } ns => $"{GeneratedCodeUtilities.CodeGeneratorName}.{ns}",
_ => GeneratedCodeUtilities.CodeGeneratorName
};
public string GeneratedNamespace => GeneratedCodeUtilities.GetGeneratedNamespaceName(Type);

public string Name => Type.Name;

Expand Down
2 changes: 1 addition & 1 deletion src/Orleans.CodeGenerator/SerializerFactoryGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ ITypeSymbol CloseType(ITypeSymbol symbol)

INamedTypeSymbol? ResolveImplementation(string name)
{
var metadataName = $"{SerializerGenerator.GetGeneratedNamespaceName(named)}.{name}"
var metadataName = $"{GeneratedCodeUtilities.GetMetadataNamespaceName(SerializerGenerator.GetGeneratedNamespaceName(named))}.{name}"
+ (named.TypeArguments.Length > 0 ? $"`{named.TypeArguments.Length}" : "");
var implementation = implementationCompilation.GetTypeByMetadataName(metadataName);
return implementation is { IsGenericType: true } ? implementation.Construct([.. implementationType.TypeArguments]) : implementation;
Expand Down
6 changes: 1 addition & 5 deletions src/Orleans.CodeGenerator/SerializerGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,7 @@ private static bool HasBaseCodec(ISerializableTypeDescription type)

public static string GetSimpleClassName(string name) => $"Codec_{name}";

public static string GetGeneratedNamespaceName(ITypeSymbol type) => type.GetNamespaceAndNesting() switch
{
{ Length: > 0 } ns => $"{GeneratedCodeUtilities.CodeGeneratorName}.{ns}",
_ => GeneratedCodeUtilities.CodeGeneratorName
};
public static string GetGeneratedNamespaceName(ITypeSymbol type) => GeneratedCodeUtilities.GetGeneratedNamespaceName(type);

internal static string GetServiceExpression(string serviceType, string codecProvider)
=> $"OrleansGeneratedCodeHelper.GetService<{serviceType}>(this, {codecProvider})";
Expand Down
101 changes: 101 additions & 0 deletions test/Orleans.CodeGenerator.Tests/ReservedIdentifierNamespaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,107 @@ public sealed class Data
Assert.Equal($"OrleansCodeGen.@{assemblyName}", namespaceDeclaration.Name.ToString());
}

[Theory]
[InlineData("@event")]
[InlineData("Outer.@class.Inner")]
public async Task NamespaceWithReservedKeywordProducesValidGeneratedCode(string namespaceName)
{
var code = $$"""
using System.Threading.Tasks;
using Orleans;

namespace {{namespaceName}};

[GenerateSerializer]
public enum Payload : int { Zero, One }

[GenerateSerializer]
public class Data
{
[Id(0)]
public Payload Value { get; set; }
}

[GenerateSerializer]
public sealed class Derived : Data
{
[Id(0)]
public int Other { get; set; }
}

[GenerateSerializer]
public sealed class Generic<T>
{
[Id(0)]
public T Value { get; set; } = default!;
}

public interface IMyGrain : IGrainWithIntegerKey
{
Task<Data> Get(Payload payload);
}
""";

var cancellationToken = TestContext.Current.CancellationToken;
var compilation = await TestCompilationHelper.CreateCompilation(code);
AssertNoErrors(compilation.GetDiagnostics(cancellationToken));

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

driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generatorDiagnostics, cancellationToken);
AssertNoErrors(generatorDiagnostics);
AssertNoErrors(outputCompilation.GetDiagnostics(cancellationToken));

// Invokables are matched by metadata name when the manifest is written, so a mismatch drops them without a compile error.
var metadataSource = driver.GetRunResult().Results
.Single()
.GeneratedSources
.Single(source => source.HintName.Contains(".orleans.metadata.g.cs", StringComparison.Ordinal));
Assert.Contains("Invokable_IMyGrain", metadataSource.SourceText.ToString(), StringComparison.Ordinal);

// Metadata names don't contain the escape character.
var generatedNamespace = "OrleansCodeGen." + namespaceName.Replace("@", string.Empty, StringComparison.Ordinal);
Assert.NotNull(outputCompilation.GetTypeByMetadataName($"{generatedNamespace}.Codec_Payload"));
Assert.NotNull(outputCompilation.GetTypeByMetadataName($"{generatedNamespace}.Codec_Generic`1"));
Assert.NotNull(outputCompilation.GetTypeByMetadataName($"{generatedNamespace}.Proxy_IMyGrain"));
}

[Fact]
public async Task TypeNestedInReservedKeywordProducesValidGeneratedCode()
{
const string code = """
using Orleans;

namespace TestProject;

public static class @default
{
[GenerateSerializer]
public sealed class Nested
{
[Id(0)]
public int Value { get; set; }
}
}
""";

var cancellationToken = TestContext.Current.CancellationToken;
var compilation = await TestCompilationHelper.CreateCompilation(code);
AssertNoErrors(compilation.GetDiagnostics(cancellationToken));

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

driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generatorDiagnostics, cancellationToken);
AssertNoErrors(generatorDiagnostics);
AssertNoErrors(outputCompilation.GetDiagnostics(cancellationToken));
}

private static void AssertNoErrors(IEnumerable<Diagnostic> diagnostics)
{
var errors = diagnostics
Expand Down