diff --git a/src/Controls/src/BindingSourceGen/ITypeSymbolExtensions.cs b/src/Controls/src/BindingSourceGen/ITypeSymbolExtensions.cs index 43ea23d060b6..c612c9728b63 100644 --- a/src/Controls/src/BindingSourceGen/ITypeSymbolExtensions.cs +++ b/src/Controls/src/BindingSourceGen/ITypeSymbolExtensions.cs @@ -5,6 +5,11 @@ namespace Microsoft.Maui.Controls.BindingSourceGen; public static class ITypeSymbolExtensions { + static readonly SymbolDisplayFormat FullyQualifiedNullableFormat = + SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions( + SymbolDisplayFormat.FullyQualifiedFormat.MiscellaneousOptions + | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + public static bool IsTypeNullable(this ITypeSymbol typeInfo, bool enabledNullable) { if (!enabledNullable && typeInfo.IsReferenceType) @@ -39,10 +44,16 @@ private static string GetGlobalName(this ITypeSymbol typeSymbol, bool isNullable if (isNullable && isValueType) { // Strips the "?" from the type name - return ((INamedTypeSymbol)typeSymbol).TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + return ((INamedTypeSymbol)typeSymbol).TypeArguments[0].ToDisplayString(FullyQualifiedNullableFormat); } - return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var globalName = typeSymbol.ToDisplayString(FullyQualifiedNullableFormat); + + // Keep nullable annotations in generic arguments but avoid nullable top-level type syntax (e.g. typeof(Foo?)). + if (globalName.EndsWith("?", StringComparison.Ordinal)) + globalName = globalName.Substring(0, globalName.Length - 1); + + return globalName; } /// diff --git a/src/Controls/src/SourceGen/NodeSGExtensions.cs b/src/Controls/src/SourceGen/NodeSGExtensions.cs index 374121063a2a..81903da3ca27 100644 --- a/src/Controls/src/SourceGen/NodeSGExtensions.cs +++ b/src/Controls/src/SourceGen/NodeSGExtensions.cs @@ -483,7 +483,7 @@ public static string ConvertWithConverter(this ValueNode valueNode, ITypeSymbol if (targetType.IsReferenceType || targetType.NullableAnnotation == NullableAnnotation.Annotated) return $"((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new {typeConverter.ToFQDisplayString()}()).ConvertFromInvariantString(\"{valueString}\", {serviceProvider.ValueAccessor}) as {targetType.ToFQDisplayString()}"; else - return $"({targetType.ToFQDisplayString()})((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new {typeConverter.ToFQDisplayString()}()).ConvertFromInvariantString(\"{valueString}\", {serviceProvider.ValueAccessor})"; + return $"({targetType.ToFQDisplayString()})((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new {typeConverter.ToFQDisplayString()}()).ConvertFromInvariantString(\"{valueString}\", {serviceProvider.ValueAccessor})!"; } else //should never happen. there's no point to implement IExtendedTypeConverter AND accept empty service provider return $"((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new {typeConverter.ToFQDisplayString()}()).ConvertFromInvariantString(\"{valueString}\", null) as {targetType.ToFQDisplayString()}"; @@ -699,4 +699,4 @@ public static (IFieldSymbol?, IPropertySymbol?) GetFieldOrBP(ITypeSymbol owner, public static bool RepresentsType(this INode node, string namespaceUri, string name) => node is ElementNode elementNode && elementNode.XmlType.RepresentsType(namespaceUri, name); -} \ No newline at end of file +} diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui34130.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui34130.xaml new file mode 100644 index 000000000000..de4188c414b0 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui34130.xaml @@ -0,0 +1,11 @@ + + + +