Skip to content

Commit

Permalink
Add UnsafeAccessor support for generic types
Browse files Browse the repository at this point in the history
Group UnsafeAccessor methods by target type
Generate fully qualified lambda signatures
Generate shorter UnsafeAccessor names
Reenable test code that uses the compiled model
Don't use Roslyn Formatter or Simplifier to make generation faster and avoid artifacts

Fixes #33773
Fixes #33470
Fixes #34057
  • Loading branch information
AndriySvyryd committed Jul 3, 2024
1 parent 4649fb3 commit a7b0cb1
Show file tree
Hide file tree
Showing 191 changed files with 69,861 additions and 46,639 deletions.
13 changes: 1 addition & 12 deletions src/EFCore.Design/Design/Internal/CSharpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1565,18 +1565,7 @@ public virtual IEnumerable<string> GetRequiredUsings(Type type)
=> type.GetNamespaces();

private string ToSourceCode(SyntaxNode node)
{
var code = node.NormalizeWhitespace().ToFullString();
var document = _project.AddDocument("Code.cs", SourceText.From(code));

var syntaxRootFoo = document.GetSyntaxRootAsync().Result!;
var annotatedDocument = document.WithSyntaxRoot(syntaxRootFoo.WithAdditionalAnnotations(Simplifier.Annotation));
document = Simplifier.ReduceAsync(annotatedDocument).Result;

var simplifiedCode = document.GetTextAsync().Result.ToString();

return simplifiedCode;
}
=> node.NormalizeWhitespace().ToFullString();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
20 changes: 0 additions & 20 deletions src/EFCore.Design/Design/Internal/DbContextOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,27 +320,7 @@ private IReadOnlyList<string> PrecompileQueries(string? outputDir, DbContext con
}

var writtenFiles = new List<string>();
foreach (var generatedFile in generatedFiles)
{
generatedFile.Code = FormatCode(project, generatedFile).GetAwaiter().GetResult().ToString()!;
}

return CompiledModelScaffolder.WriteFiles(generatedFiles, outputDir);

static async Task<object> FormatCode(Project project, ScaffoldedFile generatedFile)
{
var document = project.AddDocument("_EfGeneratedInterceptors.cs", generatedFile.Code);

// Run the simplifier to e.g. get rid of unneeded parentheses
var syntaxRoot = (await document.GetSyntaxRootAsync().ConfigureAwait(false))!;
var annotatedDocument = document.WithSyntaxRoot(syntaxRoot.WithAdditionalAnnotations(Simplifier.Annotation));
document = await Simplifier.ReduceAsync(annotatedDocument, optionSet: null).ConfigureAwait(false);
document = await Formatter.FormatAsync(document, options: null).ConfigureAwait(false);

var finalSyntaxTree = (await document.GetSyntaxTreeAsync().ConfigureAwait(false))!;
var finalText = await finalSyntaxTree.GetTextAsync().ConfigureAwait(false);
return finalText;
}
}

private string? GetNamespaceFromOutputPath(string directoryPath)
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Design/Query/Internal/CSharpToLinqTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,7 @@ private Type ResolveType(ITypeSymbol typeSymbol, Dictionary<string, Type>? gener
var properties = anonymousTypeSymbol.GetMembers().OfType<IPropertySymbol>().ToArray();
var found = _anonymousTypeDefinitions.TryGetValue(properties.Select(p => p.Name).ToArray(),
out var anonymousTypeGenericDefinition);
Debug.Assert(found, "Anonymous type not found");
Check.DebugAssert(found, "Anonymous type not found");

var constructorParameters = anonymousTypeGenericDefinition!.GetConstructors()[0].GetParameters();
var genericTypeArguments = new Type[constructorParameters.Length];
Expand Down
40 changes: 18 additions & 22 deletions src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,16 +444,6 @@ Expression VisitAssignment(BinaryExpression assignment, SyntaxKind kind)
/// </summary>
public static string GetUnsafeAccessorName(MemberInfo member)
{
StringBuilder stringBuilder = new();
stringBuilder.Clear().Append("UnsafeAccessor_");

if (member.DeclaringType?.Namespace?.Replace(".", "_") is string typeNamespace)
{
stringBuilder.Append(typeNamespace).Append('_');
}

stringBuilder.Append(member.DeclaringType!.Name.Replace("`", "")).Append('_');

// If this is the backing field of an auto-property, extract the name of the property from its compiler-generated name
// (e.g. <Name>k__BackingField)
var memberName = member.Name;
Expand All @@ -464,9 +454,8 @@ public static string GetUnsafeAccessorName(MemberInfo member)
memberName = memberName[1..pos];
}

stringBuilder.Append(memberName);

return stringBuilder.ToString();
var first = memberName[0];
return !char.IsUpper(first) ? char.ToUpperInvariant(first) + memberName[1..] : memberName;
}

/// <inheritdoc />
Expand Down Expand Up @@ -1321,12 +1310,11 @@ protected virtual TypeSyntax Generate(Type type)
{
if (type.IsGenericType)
{
// This should produce terser code, but currently gets broken by the Simplifier
//if (type.IsConstructedGenericType
// && type.GetGenericTypeDefinition() == typeof(Nullable<>))
//{
// return NullableType(Translate(type.GenericTypeArguments[0]));
//}
if (type.IsConstructedGenericType
&& type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return NullableType(Generate(type.GenericTypeArguments[0]));
}

var generic = GenericName(
Identifier(type.Name.Substring(0, type.Name.IndexOf('`'))),
Expand Down Expand Up @@ -1466,29 +1454,37 @@ protected override Expression VisitLambda<T>(Expression<T> lambda)
stackFrame.VariableNames.Add(name);
}

var body = (CSharpSyntaxNode)Translate(lambda.Body);
var body = Translate(lambda.Body);
var expressionBody = body as ExpressionSyntax;
var blockBody = body as BlockSyntax;

// If the lambda body was an expression that had lifted statements (e.g. some block in expression context), we need to create
// a block to contain these statements
if (_liftedState.Statements.Count > 0)
{
Check.DebugAssert(lambda.ReturnType != typeof(void), "lambda.ReturnType != typeof(void)");
Check.DebugAssert(expressionBody != null, "expressionBody != null");

body = Block(_liftedState.Statements.Append(ReturnStatement((ExpressionSyntax)body)));
blockBody = Block(_liftedState.Statements.Append(ReturnStatement(expressionBody)));
expressionBody = null;
_liftedState.Statements.Clear();
}

// Note that we always explicitly include the parameters' types.
// This is because in some cases, the parameter isn't actually used in the lambda body, and the compiler can't infer its type.
// However, we can't do that when the type is anonymous.
Result = ParenthesizedLambdaExpression(
attributeLists: List<AttributeListSyntax>(),
modifiers: TokenList(),
returnType: lambda.ReturnType == typeof(void) ? null : Generate(lambda.ReturnType),
ParameterList(
SeparatedList(
lambda.Parameters.Select(
p =>
Parameter(Identifier(LookupVariableName(p)))
.WithType(p.Type.IsAnonymousType() ? null : Generate(p.Type))))),
body);
blockBody,
expressionBody);

var popped = _stack.Pop();
Check.DebugAssert(popped.Equals(stackFrame), "popped.Equals(stackFrame)");
Expand Down
Loading

0 comments on commit a7b0cb1

Please sign in to comment.