Skip to content

Commit 782f48f

Browse files
authored
Refactor XAML SourceGen TypeConverters (#31467)
* Refactor SG TypeConverters * Remove some dead code * Rename ITypeConverter to ISGTypeConverter Renamed the ITypeConverter interface and its usages to ISGTypeConverter for clarity and consistency in source generator type converters. Updated references in BaseTypeConverter and TypeConverterRegistry accordingly. * Remove BaseTypeConverter * Lazy loading * Build error * Fix test * Small optimization * Update ColorConverter.cs * Apply latest context.ProjectItem changes * Make KnownNamedColors case insensitive * Use GetTypeByMetadataName and ToFQDisplayString * Rgba Argb whatever
1 parent 391b33d commit 782f48f

28 files changed

+1306
-944
lines changed

src/Controls/src/SourceGen/KnownTypeConverters.cs

Lines changed: 0 additions & 917 deletions
This file was deleted.

src/Controls/src/SourceGen/NodeSGExtensions.cs

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.CodeDom.Compiler;
3+
using System.Collections.Concurrent;
34
using System.Collections.Generic;
45
using System.Collections.Immutable;
56
using System.Diagnostics;
@@ -10,6 +11,7 @@
1011
using Microsoft.CodeAnalysis.CSharp;
1112
using Microsoft.CodeAnalysis.CSharp.Syntax;
1213
using Microsoft.Maui.Controls.Xaml;
14+
using Microsoft.Maui.Controls.SourceGen.TypeConverters;
1315

1416
namespace Microsoft.Maui.Controls.SourceGen;
1517

@@ -20,38 +22,59 @@ static class NodeSGExtensions
2022

2123
public delegate bool ProvideValueDelegate(IElementNode markupNode, SourceGenContext context, out ITypeSymbol? returnType, out string value);
2224

25+
// Lazy converter factory function
26+
static ConverterDelegate CreateLazyConverter<T>() where T : ISGTypeConverter, new() =>
27+
(value, node, toType, context, parentVar) =>
28+
lazyConverters.GetOrAdd(typeof(T), _ => new T()).Convert(value, node, toType, context, parentVar);
29+
30+
static readonly ConcurrentDictionary<Type, ISGTypeConverter> lazyConverters = new();
31+
static readonly ConcurrentDictionary<string, ISGTypeConverter> lazyRegistryConverters = new();
32+
33+
// Lazy registry-based converter function (for non-source-gen converters)
34+
private static ConverterDelegate CreateLazyRegistryConverter(string typeName) =>
35+
(value, node, toType, context, parentVar) =>
36+
{
37+
var converter = lazyRegistryConverters.GetOrAdd(typeName, name => TypeConverterRegistry.GetConverter(name)!);
38+
return converter?.Convert(value, node, toType, context, parentVar) ?? "default";
39+
};
40+
41+
// Lazy enum converter
42+
private static readonly Lazy<EnumConverter> _lazyEnumConverter = new(() => new EnumConverter());
43+
44+
private static string ConvertEnum(string value, BaseNode node, ITypeSymbol toType, SourceGenContext context, LocalVariable? parentVar = null) =>
45+
_lazyEnumConverter.Value.Convert(value, node, toType, context, parentVar);
2346

2447
static Dictionary<ITypeSymbol, (ConverterDelegate converter, ITypeSymbol returnType)> GetKnownSGTypeConverters(SourceGenContext context)
2548
=> context.knownSGTypeConverters ??= new(SymbolEqualityComparer.Default)
2649
{
27-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Converters.RectTypeConverter")!, (KnownTypeConverters.ConvertRect, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Rect")!) },
28-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Converters.ColorTypeConverter")!, (KnownTypeConverters.ConvertColor, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Color")!) },
29-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Converters.PointTypeConverter")!, (KnownTypeConverters.ConvertPoint, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Point")!) },
30-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.ThicknessTypeConverter")!, (KnownTypeConverters.ConvertThickness, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Thickness")!) },
31-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.CornerRadiusTypeConverter")!, (KnownTypeConverters.ConvertCornerRadius, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.CornerRadius")!) },
32-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.EasingTypeConverter")!, (KnownTypeConverters.ConvertEasing, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Easing")!) },
33-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexJustifyTypeConverter")!, (KnownTypeConverters.ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexJustify")!) },
34-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexDirectionTypeConverter")!, (KnownTypeConverters.ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexDirection")!) },
35-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexAlignContentTypeConverter")!, (KnownTypeConverters.ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexAlignContent")!) },
36-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexAlignItemsTypeConverter")!, (KnownTypeConverters.ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexAlignItems")!) },
37-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexAlignSelfTypeConverter")!, (KnownTypeConverters.ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexAlignSelf")!) },
38-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexWrapTypeConverter")!, (KnownTypeConverters.ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexWrap")!) },
39-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexBasisTypeConverter")!, (KnownTypeConverters.ConvertFlexBasis, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexBasis")!) },
40-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ColumnDefinitionCollectionTypeConverter")!, (KnownTypeConverters.ConvertColumnDefinitionCollection, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ColumnDefinitionCollection")!) },
41-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.FlowDirectionConverter")!, (KnownTypeConverters.ConvertFlowDirection, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.FlowDirection")!) },
42-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.FontSizeConverter")!, (KnownTypeConverters.ConvertFontSize, context.Compilation.GetTypeByMetadataName("System.Double")!) },
43-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.GridLengthTypeConverter")!, (KnownTypeConverters.ConvertGridLength, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.GridLength")!) },
44-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ImageSourceConverter")!, (KnownTypeConverters.ConvertImageSource, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ImageSource")!) },
45-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.LayoutOptionsConverter")!, (KnownTypeConverters.ConvertLayoutOptions, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.LayoutOptions")!) },
46-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ListStringTypeConverter")!, (KnownTypeConverters.ConvertListString, context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IList`1")!.Construct(context.Compilation.GetSpecialType(SpecialType.System_String))!) },
47-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ResourceDictionary+RDSourceTypeConverter")!, (KnownTypeConverters.ConvertRDSource, context.Compilation.GetTypeByMetadataName("System.Uri")!) },
48-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.RowDefinitionCollectionTypeConverter")!, (KnownTypeConverters.ConvertRowDefinitionCollection, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.RowDefinitionCollection")!) },
49-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.PointCollectionConverter")!, (KnownTypeConverters.ConvertPointCollection, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.PointCollection")!) },
50-
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Compatibility.ConstraintTypeConverter")!, (KnownTypeConverters.ConvertConstraint, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Compatibility.Constraint")!) },
50+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Converters.RectTypeConverter")!, (CreateLazyConverter<RectConverter>(), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Rect")!) },
51+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Converters.ColorTypeConverter")!, (CreateLazyConverter<ColorConverter>(), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Color")!) },
52+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Converters.PointTypeConverter")!, (CreateLazyConverter<PointConverter>(), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Graphics.Point")!) },
53+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.ThicknessTypeConverter")!, (CreateLazyConverter<ThicknessConverter>(), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Thickness")!) },
54+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.CornerRadiusTypeConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.CornerRadius"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.CornerRadius")!) },
55+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.EasingTypeConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.Easing"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Easing")!) },
56+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexJustifyTypeConverter")!, (ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexJustify")!) },
57+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexDirectionTypeConverter")!, (ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexDirection")!) },
58+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexAlignContentTypeConverter")!, (ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexAlignContent")!) },
59+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexAlignItemsTypeConverter")!, (ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexAlignItems")!) },
60+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexAlignSelfTypeConverter")!, (ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexAlignSelf")!) },
61+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexWrapTypeConverter")!, (ConvertEnum, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexWrap")!) },
62+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Converters.FlexBasisTypeConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.Layouts.FlexBasis"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Layouts.FlexBasis")!) },
63+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ColumnDefinitionCollectionTypeConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.Controls.ColumnDefinitionCollection"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ColumnDefinitionCollection")!) },
64+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.FlowDirectionConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.FlowDirection"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.FlowDirection")!) },
65+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.FontSizeConverter")!, (CreateLazyRegistryConverter("System.Double"), context.Compilation.GetTypeByMetadataName("System.Double")!) },
66+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.GridLengthTypeConverter")!, (CreateLazyConverter<GridLengthConverter>(), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.GridLength")!) },
67+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ImageSourceConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.Controls.ImageSource"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ImageSource")!) },
68+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.LayoutOptionsConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.Controls.LayoutOptions"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.LayoutOptions")!) },
69+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ListStringTypeConverter")!, (CreateLazyRegistryConverter("System.Collections.Generic.IList`1[System.String]"), context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IList`1")!.Construct(context.Compilation.GetSpecialType(SpecialType.System_String))!) },
70+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.ResourceDictionary+RDSourceTypeConverter")!, (CreateLazyRegistryConverter("System.Uri"), context.Compilation.GetTypeByMetadataName("System.Uri")!) },
71+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.RowDefinitionCollectionTypeConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.Controls.RowDefinitionCollection"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.RowDefinitionCollection")!) },
72+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.PointCollectionConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.Controls.PointCollection"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.PointCollection")!) },
73+
{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Compatibility.ConstraintTypeConverter")!, (CreateLazyRegistryConverter("Microsoft.Maui.Controls.Compatibility.Constraint"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Compatibility.Constraint")!) },
5174

5275
// TODO: PathFigureCollectionConverter (used for PathGeometry and StrokeShape) is very complex, skipping for now, apart from that one all other shapes work though
53-
//{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.PathGeometryConverter")!, (KnownTypeConverters.ConvertPathGeometry, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.Geometry")!) },
54-
//{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.StrokeShapeTypeConverter")!, (KnownTypeConverters.ConvertStrokeShape, context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.Shape")!) },
76+
//{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.PathGeometryConverter")!, (CreateRegistryConverter("Microsoft.Maui.Controls.Shapes.Geometry"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.Geometry")!) },
77+
//{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.StrokeShapeTypeConverter")!, (CreateRegistryConverter("Microsoft.Maui.Controls.Shapes.Shape"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.Shape")!) },
5578
};
5679

5780
public static Dictionary<ITypeSymbol, ProvideValueDelegate> GetKnownValueProviders(SourceGenContext context)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Xml;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.Maui.Controls.Xaml;
4+
using static Microsoft.Maui.Controls.SourceGen.LocationHelpers;
5+
6+
namespace Microsoft.Maui.Controls.SourceGen.TypeConverters;
7+
8+
/// <summary>
9+
/// Extension methods for SourceGenContext to provide diagnostic reporting functionality for type converters.
10+
/// </summary>
11+
internal static class SourceGenContextExtensions
12+
{
13+
/// <summary>
14+
/// Reports a conversion failure diagnostic.
15+
/// </summary>
16+
/// <param name="context">The source generation context</param>
17+
/// <param name="xmlLineInfo">XML line information for location</param>
18+
/// <param name="value">The value that failed to convert</param>
19+
/// <param name="descriptor">The diagnostic descriptor</param>
20+
public static void ReportConversionFailed(this SourceGenContext context, IXmlLineInfo xmlLineInfo, string value, DiagnosticDescriptor descriptor)
21+
{
22+
context.ReportDiagnostic(Diagnostic.Create(descriptor, LocationCreate(context.ProjectItem.RelativePath!, xmlLineInfo, value), value));
23+
}
24+
25+
/// <summary>
26+
/// Reports a conversion failure diagnostic with target type information.
27+
/// </summary>
28+
/// <param name="context">The source generation context</param>
29+
/// <param name="xmlLineInfo">XML line information for location</param>
30+
/// <param name="value">The value that failed to convert</param>
31+
/// <param name="toType">The target type for conversion</param>
32+
/// <param name="descriptor">The diagnostic descriptor</param>
33+
public static void ReportConversionFailed(this SourceGenContext context, IXmlLineInfo xmlLineInfo, string value, ITypeSymbol? toType, DiagnosticDescriptor descriptor)
34+
{
35+
#pragma warning disable RS0030 // Do not use banned APIs
36+
context.ReportDiagnostic(Diagnostic.Create(descriptor, LocationCreate(context.ProjectItem.RelativePath!, xmlLineInfo, value), value, toType?.ToDisplayString()));
37+
#pragma warning restore RS0030 // Do not use banned APIs
38+
}
39+
40+
/// <summary>
41+
/// Reports a conversion failure diagnostic with additional information and target type.
42+
/// </summary>
43+
/// <param name="context">The source generation context</param>
44+
/// <param name="xmlLineInfo">XML line information for location</param>
45+
/// <param name="value">The value that failed to convert</param>
46+
/// <param name="additionalInfo">Additional context information</param>
47+
/// <param name="toType">The target type for conversion</param>
48+
/// <param name="descriptor">The diagnostic descriptor</param>
49+
public static void ReportConversionFailed(this SourceGenContext context, IXmlLineInfo xmlLineInfo, string value, string additionalInfo, ITypeSymbol? toType, DiagnosticDescriptor descriptor)
50+
{
51+
#pragma warning disable RS0030 // Do not use banned APIs
52+
context.ReportDiagnostic(Diagnostic.Create(descriptor, LocationCreate(context.ProjectItem.RelativePath!, xmlLineInfo, value), value, additionalInfo, toType?.ToDisplayString()));
53+
#pragma warning restore RS0030 // Do not use banned APIs
54+
}
55+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections.Generic;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.Maui.Controls.Xaml;
4+
5+
namespace Microsoft.Maui.Controls.SourceGen.TypeConverters;
6+
7+
internal class BindablePropertyConverter : ISGTypeConverter
8+
{
9+
public IEnumerable<string> SupportedTypes => new[] { "BindableProperty", "Microsoft.Maui.Controls.BindableProperty" };
10+
11+
public string Convert(string value, BaseNode node, ITypeSymbol toType, SourceGenContext context, LocalVariable? parentVar = null)
12+
{
13+
var parts = value.Split(['.']);
14+
15+
if (parts.Length != 2)
16+
{
17+
// reportDiagnostic(Diagnostic.Create(Descriptors.BindablePropertyConversionFailed, LocationCreate(filePath, xmlLineInfo, value), value));
18+
return "default";
19+
}
20+
21+
if (parts.Length == 2)
22+
{
23+
var typesymbol = parts[0]!.GetTypeSymbol(context.ReportDiagnostic, context.Compilation, context.XmlnsCache, null!)!;
24+
25+
var name = parts[1];
26+
return typesymbol.GetBindableProperty("", ref name, out _, context, node)!.ToFQDisplayString();
27+
}
28+
return "null";
29+
}
30+
}

0 commit comments

Comments
 (0)