Skip to content

Commit

Permalink
Support to change the accessibility of a model and change a model to …
Browse files Browse the repository at this point in the history
…be struct via customized code (#4347)

Fixes #4259 
Fixes #4346 

I change the structure of test data for those customization code related
test cases, now it should be a directory with the name of the test
method, in which we could put all of customization code because in quite
a few scenarios we might need multiple files there.

---------

Co-authored-by: m-nash <64171366+m-nash@users.noreply.github.com>
  • Loading branch information
ArcturusZhang and m-nash authored Sep 11, 2024
1 parent a5fc2ce commit 7d444a4
Show file tree
Hide file tree
Showing 21 changed files with 286 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Microsoft.Generator.CSharp.Customization
{
[AttributeUsage(AttributeTargets.Class)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class CodeGenTypeAttribute : Attribute
{
public string? OriginalName { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public virtual void AddVisitor(LibraryVisitor visitor)
private SourceInputModel? _sourceInputModel;
internal async Task InitializeSourceInputModelAsync()
{
GeneratedCodeWorkspace existingCode = GeneratedCodeWorkspace.CreateExistingCodeProject(Instance.Configuration.ProjectDirectory, Instance.Configuration.ProjectGeneratedDirectory);
GeneratedCodeWorkspace existingCode = GeneratedCodeWorkspace.CreateExistingCodeProject([Instance.Configuration.ProjectDirectory], Instance.Configuration.ProjectGeneratedDirectory);
_sourceInputModel = new SourceInputModel(await existingCode.GetCompilationAsync());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,19 @@ internal static async Task<GeneratedCodeWorkspace> Create()
return new GeneratedCodeWorkspace(generatedCodeProject);
}

internal static GeneratedCodeWorkspace CreateExistingCodeProject(string projectDirectory, string generatedDirectory)
internal static GeneratedCodeWorkspace CreateExistingCodeProject(IEnumerable<string> projectDirectories, string generatedDirectory)
{
var workspace = new AdhocWorkspace();
var newOptionSet = workspace.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, _newLine);
workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(newOptionSet));
Project project = workspace.AddProject("ExistingCode", LanguageNames.CSharp);

if (Path.IsPathRooted(projectDirectory))
foreach (var projectDirectory in projectDirectories)
{
projectDirectory = Path.GetFullPath(projectDirectory);

project = AddDirectory(project, projectDirectory, skipPredicate: sourceFile => sourceFile.StartsWith(generatedDirectory));
if (Path.IsPathRooted(projectDirectory))
{
project = AddDirectory(project, Path.GetFullPath(projectDirectory), skipPredicate: sourceFile => sourceFile.StartsWith(generatedDirectory));
}
}

project = project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ public sealed class ModelProvider : TypeProvider

protected override FormattableString Description { get; }

private readonly bool _isStruct;
private readonly TypeSignatureModifiers _declarationModifiers;
private readonly CSharpType _privateAdditionalRawDataPropertyType = typeof(IDictionary<string, BinaryData>);
private readonly Type _additionalPropsUnknownType = typeof(BinaryData);
private readonly Lazy<TypeProvider?>? _baseTypeProvider;
Expand All @@ -38,27 +36,12 @@ public ModelProvider(InputModelType inputModel)
{
_inputModel = inputModel;
Description = inputModel.Description != null ? FormattableStringHelpers.FromString(inputModel.Description) : $"The {Name}.";
_declarationModifiers = TypeSignatureModifiers.Partial |
(inputModel.ModelAsStruct ? TypeSignatureModifiers.ReadOnly | TypeSignatureModifiers.Struct : TypeSignatureModifiers.Class);

if (inputModel.Access == "internal")
{
_declarationModifiers |= TypeSignatureModifiers.Internal;
}

bool isAbstract = inputModel.DiscriminatorProperty is not null && inputModel.DiscriminatorValue is null;
if (isAbstract)
{
_declarationModifiers |= TypeSignatureModifiers.Abstract;
}

if (inputModel.BaseModel is not null)
{
_baseTypeProvider = new(() => CodeModelPlugin.Instance.TypeFactory.CreateModel(inputModel.BaseModel));
DiscriminatorValueExpression = EnsureDiscriminatorValueExpression();
}

_isStruct = inputModel.ModelAsStruct;
}

public bool IsUnknownDiscriminatorModel => _inputModel.IsUnknownDiscriminatorModel;
Expand Down Expand Up @@ -107,7 +90,54 @@ protected override TypeProvider[] BuildSerializationProviders()

protected override string BuildName() => _inputModel.Name.ToCleanName();

protected override TypeSignatureModifiers GetDeclarationModifiers() => _declarationModifiers;
protected override TypeSignatureModifiers GetDeclarationModifiers()
{
var customCodeModifiers = CustomCodeView?.DeclarationModifiers ?? TypeSignatureModifiers.None;
var isStruct = false;
// the information of if this model should be a struct comes from two sources:
// 1. the customied code
// 2. the spec
if (customCodeModifiers.HasFlag(TypeSignatureModifiers.Struct))
{
isStruct = true;
}
if (_inputModel.ModelAsStruct)
{
isStruct = true;
}
var declarationModifiers = TypeSignatureModifiers.Partial;

if (isStruct)
{
declarationModifiers |= TypeSignatureModifiers.ReadOnly | TypeSignatureModifiers.Struct;
}
else
{
declarationModifiers |= TypeSignatureModifiers.Class;
}

if (customCodeModifiers != TypeSignatureModifiers.None)
{
declarationModifiers |= GetAccessibilityModifiers(customCodeModifiers);
}
else if (_inputModel.Access == "internal")
{
declarationModifiers |= TypeSignatureModifiers.Internal;
}

bool isAbstract = _inputModel.DiscriminatorProperty is not null && _inputModel.DiscriminatorValue is null;
if (isAbstract)
{
declarationModifiers |= TypeSignatureModifiers.Abstract;
}

return declarationModifiers;

static TypeSignatureModifiers GetAccessibilityModifiers(TypeSignatureModifiers modifiers)
{
return modifiers & (TypeSignatureModifiers.Public | TypeSignatureModifiers.Internal | TypeSignatureModifiers.Protected | TypeSignatureModifiers.Private);
}
}

/// <summary>
/// Builds the fields for the model by adding the raw data field.
Expand Down Expand Up @@ -318,15 +348,15 @@ private ConstructorProvider BuildFullConstructor()
// add the base parameters, if any
foreach (var property in baseProperties)
{
AddInitializationParameterForCtor(baseParameters, property, _isStruct, isPrimaryConstructor);
AddInitializationParameterForCtor(baseParameters, property, Type.IsStruct, isPrimaryConstructor);
}

// construct the initializer using the parameters from base signature
var constructorInitializer = new ConstructorInitializer(true, [.. baseParameters.Select(GetExpression)]);

foreach (var property in Properties)
{
AddInitializationParameterForCtor(constructorParameters, property, _isStruct, isPrimaryConstructor);
AddInitializationParameterForCtor(constructorParameters, property, Type.IsStruct, isPrimaryConstructor);
}

constructorParameters.AddRange(_inputModel.IsUnknownDiscriminatorModel ? baseParameters : baseParameters.Where(p => p.Property is null || !p.Property.IsDiscriminator));
Expand Down Expand Up @@ -455,7 +485,7 @@ private MethodBodyStatement GetPropertyInitializers(

ValueExpression? initializationValue = null;

if (parameterMap.TryGetValue(property.AsParameter.Name, out var parameter) || _isStruct)
if (parameterMap.TryGetValue(property.AsParameter.Name, out var parameter) || Type.IsStruct)
{
if (parameter != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,46 @@ public NamedTypeSymbolProvider(INamedTypeSymbol namedTypeSymbol)

protected override string GetNamespace() => GetFullyQualifiedNameFromDisplayString(_namedTypeSymbol.ContainingNamespace);

protected override TypeSignatureModifiers GetDeclarationModifiers()
{
var declaredModifiers = GetAccessModifiers(_namedTypeSymbol.DeclaredAccessibility);
if (_namedTypeSymbol.IsReadOnly)
{
declaredModifiers |= TypeSignatureModifiers.ReadOnly;
}
if (_namedTypeSymbol.IsStatic)
{
declaredModifiers |= TypeSignatureModifiers.Static;
}
switch (_namedTypeSymbol.TypeKind)
{
case TypeKind.Class:
declaredModifiers |= TypeSignatureModifiers.Class;
if (_namedTypeSymbol.IsSealed)
{
declaredModifiers |= TypeSignatureModifiers.Sealed;
}
break;
case TypeKind.Struct:
declaredModifiers |= TypeSignatureModifiers.Struct;
break;
case TypeKind.Interface:
declaredModifiers |= TypeSignatureModifiers.Interface;
break;
}
return declaredModifiers;

static TypeSignatureModifiers GetAccessModifiers(Accessibility accessibility) => accessibility switch
{
Accessibility.Private => TypeSignatureModifiers.Private,
Accessibility.Protected => TypeSignatureModifiers.Protected,
Accessibility.Internal => TypeSignatureModifiers.Internal,
Accessibility.Public => TypeSignatureModifiers.Public,
Accessibility.ProtectedOrInternal => TypeSignatureModifiers.Protected | TypeSignatureModifiers.Internal,
_ => TypeSignatureModifiers.None
};
}

protected override FieldProvider[] BuildFields()
{
List<FieldProvider> fields = new List<FieldProvider>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.CodeAnalysis.CSharp;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;

namespace Microsoft.Generator.CSharp.Tests
Expand All @@ -15,19 +16,24 @@ internal static class Helpers

public static string GetExpectedFromFile(string? parameters = null)
{
return File.ReadAllText(GetAssetFilePath(parameters));
return File.ReadAllText(GetAssetFileOrDirectoryPath(true, parameters));
}

private static string GetAssetFilePath(string? parameters = null)
private static string GetAssetFileOrDirectoryPath(bool isFile, string? parameters = null)
{
var stackTrace = new StackTrace();
var stackFrame = GetRealMethodInvocation(stackTrace);
var method = stackFrame.GetMethod();
var callingClass = method!.DeclaringType;
var nsSplit = callingClass!.Namespace!.Split('.');
var ns = nsSplit[^1];
var paramString = parameters is null ? string.Empty : $"({parameters})";
return Path.Combine(_assemblyLocation, ns, "TestData", callingClass.Name, $"{method.Name}{paramString}.cs");
var extName = isFile ? ".cs" : string.Empty;
var path = _assemblyLocation;
for (int i = 4; i < nsSplit.Length; i++)
{
path = Path.Combine(path, nsSplit[i]);
}
return Path.Combine(path, "TestData", callingClass.Name, $"{method.Name}{paramString}{extName}");
}

private static StackFrame GetRealMethodInvocation(StackTrace stackTrace)
Expand All @@ -36,25 +42,30 @@ private static StackFrame GetRealMethodInvocation(StackTrace stackTrace)
while (i < stackTrace.FrameCount)
{
var frame = stackTrace.GetFrame(i);
if (frame!.GetMethod()!.DeclaringType != typeof(Helpers))
var declaringType = frame!.GetMethod()!.DeclaringType!;
// we need to skip those method invocations from this class, or from the async state machine when the caller is an async method
if (declaringType != typeof(Helpers) && declaringType != typeof(MockHelpers) && !IsCompilerGenerated(declaringType))
{
return frame;
}
i++;
}

throw new InvalidOperationException($"There is no method invocation outside the {typeof(Helpers)} class in the stack trace");

static bool IsCompilerGenerated(Type type)
{
return type.IsDefined(typeof(CompilerGeneratedAttribute), false) || (type.Namespace?.StartsWith("System.Runtime.CompilerServices") ?? false) ||
type.Name.StartsWith("<<", StringComparison.Ordinal);
}
}

public static Compilation GetCompilationFromFile(string? parameters = null)
public static async Task<Compilation> GetCompilationFromDirectoryAsync(string? parameters = null)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(GetAssetFilePath(parameters)));
CSharpCompilation compilation = CSharpCompilation.Create("ExistingCode")
.WithOptions(new CSharpCompilationOptions(OutputKind.ConsoleApplication))
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(syntaxTree);

return compilation;
var directory = GetAssetFileOrDirectoryPath(false, parameters);
var codeGenAttributeFiles = Path.Combine(_assemblyLocation, "..", "..", "..", "..", "..", "Microsoft.Generator.CSharp.Customization", "src");
var workspace = GeneratedCodeWorkspace.CreateExistingCodeProject([directory, codeGenAttributeFiles], Path.Combine(directory, "Generated"));
return await workspace.GetCompilationAsync();
}
}
}
Loading

0 comments on commit 7d444a4

Please sign in to comment.