Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration tests #14

Merged
78 changes: 51 additions & 27 deletions src/Controls/src/BindingSourceGen/BindingCodeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ public sealed class BindingCodeWriter
{
public static string GeneratedCodeAttribute => $"[GeneratedCodeAttribute(\"{typeof(BindingCodeWriter).Assembly.FullName}\", \"{typeof(BindingCodeWriter).Assembly.GetName().Version}\")]";

public string GenerateCode() => $$"""
public string GenerateCode()
{
if (_bindings.Count == 0)
{
return string.Empty;
}

return DoGenerateCode();
}

private string DoGenerateCode() => $$"""
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a .NET MAUI source generator.
Expand All @@ -26,11 +36,22 @@ public string GenerateCode() => $$"""
namespace System.Runtime.CompilerServices
{
using System;
using System.CodeDom.Compiler;

{{GeneratedCodeAttribute}}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute(string filePath, int line, int column) : Attribute
file sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
{
FilePath = filePath;
Line = line;
Column = column;
}

public string FilePath { get; }
public int Line { get; }
public int Column { get; }
}
}

Expand All @@ -39,7 +60,7 @@ namespace Microsoft.Maui.Controls.Generated
using System;
using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;
using Microsoft.Maui.Controls.Internal;
using Microsoft.Maui.Controls.Internals;

{{GeneratedCodeAttribute}}
file static class GeneratedBindableObjectExtensions
Expand Down Expand Up @@ -120,7 +141,7 @@ public void AppendSetBindingInterceptor(int id, CodeWriterBinding binding)
object? targetNullValue = null)
{
var binding = new TypedBinding<{{binding.SourceType}}, {{binding.PropertyType}}>(
getter: static source => (getter(source), true),
getter: source => (getter(source), true),
""");

Indent();
Expand Down Expand Up @@ -165,31 +186,34 @@ private void AppendInterceptorAttribute(SourceCodeLocation location)
AppendLine($"[InterceptsLocationAttribute(@\"{location.FilePath}\", {location.Line}, {location.Column})]");
}

// TODO: The setter action is broken at the moment, it needs to be changed completely:
// - see https://sharplab.io/#v2:CYLg1APg9FC08MU5LVvRzBYAUDABADwCGArgC4D2sA5gKYB2dATseXcAHy4H5/4AVABYBLAM74AxpWB18Ad2IT6TVu2D4ARgE98xfADoAcgFEB+ALIBBAKoBJfGMqlmkuSpZtKzAzyh/+PgBhIWIGegkqfHJRCQAzEQAbOQBbYl1JMjE5EQZpZmY6SXItOlCANxFvPQYNeSTE0vxEyjESkTiAwJi5aVl8cXxCjzUOXzwoIigyKlpGT3VuCcwV1bX11FwAYgZSRMTiTWT8RkPk3FwAAQAmAEYLnAZiFLoxAAdiN0ttI2fXj7cuAA3rhApcAMz4G7fADKzlcdCCBzEYlB/BBOECYMhlE0ACsiuQAPz4Kz4IH4ejkADcjjoNPwAF80XxmTgWVDIdCAOocjFY/gQ/AADXwAHlyZT6bTsgzGfgALz4Xb7ACE1I5bI5Quhwr5HOx+AAmiSAEKSqky6VMzUPQ1tZikYrG/WYgWc/C4gnOoIW62y2nypUqxLqg2CyFGgAUAEpyWzAlq3Xwddd8AAtV3uj0WbQABWYlDeLHI2iRSgkABE/QyA0zFcq9qGNcmbezW6nvgWiyWy8jUa3+fw2UnoQJtMXgKbcsBcjRCAIYQAaQR5pZDlOQ8eT6e1OdRy4AVgXy9XnCl5HYzBXl1u1xPK4Ea7pl5YMazw9wSaeL3enzkFgiJIhZOHE5AGBYZAiAYQSUAw5CFokYgGAA4vMozAMCuAAJA3PcOC4QADFCtwwTIdCVpQKRkSkbxJCwLY4QA2mhqhsBwsGyFYl7MCImgUHQUYAESAcBrSUGBEFQWR8GIchu6zuEcIuG4rErgAaiwYhVAwCqkYRBgGYRK5BHs5AuHQCpMBQrCJCueakEcQEANJ0NoAiUAA1owVlNkJK5CfphmGUJMYALo4Qkxy3gAbFCaasQsHAKWcdBivihImAAHuwDDaXBA7YRiOHYSx6HscAnF0NxCF8QJwmiSBEngZBpDQbBsmUEhBgKXOykImp+Cacw+W6UFRkmWZFlWXQNnEHZ+AOU5kiue5Xk+SG/n4IFwVGaFEUEbhXK3HFlwACz4DC9K9eEtxRiV2ExIMXqEloM6pel3rkEuD0vc6mgiMATxHHQ3bFswpY/YduHHjQLSaPNIAgLmvy/gCdAQdo/VuOWKJEiucO4ojyM/H8f5uJjYO9rjYjnlSV7vodGLYSz5TEMwb17uEDZMPIggTslM5zoQhMI4kSMo2T6OY9jiL9vjlLw8Tkto/+lOFuDpY05w93Q9h9MsFDLMs7KV4gCRcVRk4Kl0CubOJKQdBxgq54PUVbvYR0+BW/CXyDCG4bZkH+AQBAji+xjZL+5QJSi8rpOqxT3IKIHwfuqH+CqvIBhij1BgABJKOp82O6nadYhn2e56aBjFw7GO+tHsdK+LJOo/8avpvgABejPGyzzP97hADsLZDwm5cCh73cGNWSr247Y/G4yMZL0m2EjkAA===
private void AppendSetterAction(IPathPart[] path)
{
AppendLine("static (source, value) => ");
AppendLine('{');
Indent();

if (path.Any(part => part.IsConditional))
{
Append("if (");
Append(_accessExpressionBuilder.BuildExpression("source", path, depth: path.Length - 1));
AppendLine($" is null)");
AppendLines(
"""
{
return;
}

""");
}

Append(_accessExpressionBuilder.BuildExpression("source", path, unsafeAccess: true));
AppendLine(" = value;");

Unindent();
Append('}');
throw new NotImplementedException();
// AppendLine("static (source, value) => ");
// AppendLine('{');
// Indent();

// if (path.Any(part => part.IsConditional))
// {
// Append("if (");
// Append(_accessExpressionBuilder.BuildExpression("source", path, depth: path.Length - 1));
// AppendLine($" is null)");
// AppendLines(
// """
// {
// return;
// }

// """);
// }

// Append(_accessExpressionBuilder.BuildExpression("source", path, unsafeAccess: true));
// AppendLine(" = value;");

// Unindent();
// Append('}');
}

private void AppendHandlersArray(TypeDescription sourceType, IPathPart[] path)
Expand Down
74 changes: 14 additions & 60 deletions src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ static bool IsSetBindingMethod(SyntaxNode node)
static BindingDiagnosticsWrapper GetBindingForGeneration(GeneratorSyntaxContext context, CancellationToken t)
{
var diagnostics = new List<Diagnostic>();
var invocation = (InvocationExpressionSyntax)context.Node;
NullableContext nullableContext = context.SemanticModel.GetNullableContext(context.Node.Span.Start);
var enabledNullable = (nullableContext & NullableContext.Enabled) == NullableContext.Enabled;

var invocation = (InvocationExpressionSyntax)context.Node;
var method = (MemberAccessExpressionSyntax)invocation.Expression;

var sourceCodeLocation = new SourceCodeLocation(
Expand All @@ -82,27 +84,25 @@ static BindingDiagnosticsWrapper GetBindingForGeneration(GeneratorSyntaxContext
return ReportDiagnostics(lambdaDiagnostics);
}

NullableContext nullableContext = context.SemanticModel.GetNullableContext(context.Node.Span.Start);
var enabledNullable = (nullableContext & NullableContext.Enabled) == NullableContext.Enabled;
var parts = new List<IPathPart>();
var correctlyParsed = PathParser.ParsePath(lambdaBody, enabledNullable, context, parts);

if (!correctlyParsed)
var lambdaTypeInfo = context.SemanticModel.GetTypeInfo(lambdaBody, t);
if (lambdaTypeInfo.Type == null)
{
return ReportDiagnostics([DiagnosticsFactory.UnableToResolvePath(lambdaBody.GetLocation())]);
return ReportDiagnostics([DiagnosticsFactory.UnableToResolvePath(lambdaBody.GetLocation())]); // TODO: New diagnostic
}

// Sometimes analysing just the return type of the lambda is not enough. TODO: Refactor
// var propertyType = BindingGenerationUtilities.CreateTypeNameFromITypeSymbol(lambdaSymbol.ReturnType, enabledNullable);
// var lastMember = parts.Last() is Cast cast ? cast.Part : parts.Last();
// propertyType = propertyType with { IsNullable = lastMember is ConditionalAccess || propertyType.IsNullable };
var pathParser = new PathParser(context);
var (pathDiagnostics, parts) = pathParser.ParsePath(lambdaBody);
if (pathDiagnostics.Length > 0)
{
return ReportDiagnostics(pathDiagnostics);
}

var codeWriterBinding = new CodeWriterBinding(
Location: sourceCodeLocation,
SourceType: BindingGenerationUtilities.CreateTypeNameFromITypeSymbol(lambdaSymbol.Parameters[0].Type, enabledNullable),
PropertyType: BindingGenerationUtilities.CreateTypeNameFromITypeSymbol(lambdaSymbol.ReturnType, enabledNullable),
PropertyType: BindingGenerationUtilities.CreateTypeNameFromITypeSymbol(lambdaTypeInfo.Type, enabledNullable),
Path: parts.ToArray(),
GenerateSetter: true //TODO: Implement
GenerateSetter: false //TODO: Implement
);
return new BindingDiagnosticsWrapper(codeWriterBinding, diagnostics.ToArray());
}
Expand Down Expand Up @@ -150,52 +150,6 @@ private static (ExpressionSyntax? lambdaBodyExpression, IMethodSymbol? lambdaSym
private static BindingDiagnosticsWrapper ReportDiagnostics(Diagnostic[] diagnostics) => new(null, diagnostics);
}

internal static class BindingGenerationUtilities
{
internal static bool IsTypeNullable(ITypeSymbol typeInfo, bool enabledNullable)
{
if (!enabledNullable && typeInfo.IsReferenceType)
{
return true;
}

if (typeInfo.NullableAnnotation == NullableAnnotation.Annotated)
{
return true;
}

return typeInfo is INamedTypeSymbol namedTypeSymbol
&& namedTypeSymbol.IsGenericType
&& namedTypeSymbol.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;
}

internal static TypeDescription CreateTypeNameFromITypeSymbol(ITypeSymbol typeSymbol, bool enabledNullable)
{
var (isNullable, name) = GetNullabilityAndName(typeSymbol, enabledNullable);
return new TypeDescription(
GlobalName: name,
IsNullable: isNullable,
IsGenericParameter: typeSymbol.Kind == SymbolKind.TypeParameter,
IsValueType: typeSymbol.IsValueType);
}

internal static (bool, string) GetNullabilityAndName(ITypeSymbol typeSymbol, bool enabledNullable)
{
if (typeSymbol.IsReferenceType && (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated || !enabledNullable))
{
return (true, typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
}

if (IsTypeNullable(typeSymbol, enabledNullable))
{
var type = ((INamedTypeSymbol)typeSymbol).TypeArguments[0];
return (true, type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
}

return (false, typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
}
}

public sealed record BindingDiagnosticsWrapper(
CodeWriterBinding? Binding,
Diagnostic[] Diagnostics);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Microsoft.CodeAnalysis;

namespace Microsoft.Maui.Controls.BindingSourceGen;

internal static class BindingGenerationUtilities
{
internal static bool IsTypeNullable(ITypeSymbol typeInfo, bool enabledNullable)
{
if (!enabledNullable && typeInfo.IsReferenceType)
{
return true;
}

if (typeInfo.NullableAnnotation == NullableAnnotation.Annotated)
{
return true;
}

return typeInfo is INamedTypeSymbol namedTypeSymbol
&& namedTypeSymbol.IsGenericType
&& namedTypeSymbol.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;
}

internal static TypeDescription CreateTypeNameFromITypeSymbol(ITypeSymbol typeSymbol, bool enabledNullable)
{
var isNullable = IsTypeNullable(typeSymbol, enabledNullable);
return new TypeDescription(
GlobalName: GetGlobalName(typeSymbol, isNullable, typeSymbol.IsValueType),
IsNullable: isNullable,
IsGenericParameter: typeSymbol.Kind == SymbolKind.TypeParameter,
IsValueType: typeSymbol.IsValueType);
}

internal static TypeDescription CreateTypeDescriptionForCast(ITypeSymbol typeSymbol)
{
// We can cast to nullable value type or non-nullable reference type
var name = typeSymbol.IsValueType ?
((INamedTypeSymbol)typeSymbol).TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) :
typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

return new TypeDescription(
GlobalName: name,
IsNullable: typeSymbol.IsValueType,
IsGenericParameter: typeSymbol.Kind == SymbolKind.TypeParameter,
IsValueType: typeSymbol.IsValueType);
}

internal static string GetGlobalName(ITypeSymbol typeSymbol, bool IsNullable, bool IsValueType)
{
if (IsNullable && IsValueType)
{
return ((INamedTypeSymbol)typeSymbol).TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}

return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
}
Loading
Loading