Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 49 additions & 17 deletions src/Controls/src/SourceGen/KnownMarkups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,11 @@ internal static bool ProvideValueForAppThemeBindingExtension(ElementNode node, I

// Get the target property type for type conversion
ITypeSymbol? propertyType = null;
IFieldSymbol? bpFieldSymbol = null;
if (node.TryGetPropertyName(node.Parent, out XmlName propertyName) && context.Variables.TryGetValue(node.Parent, out ILocalValue parentVar))
{
var localName = propertyName.LocalName;
var bpFieldSymbol = parentVar.Type.GetBindableProperty(propertyName.NamespaceURI, ref localName, out bool attached, context, node as IXmlLineInfo);
bpFieldSymbol = parentVar.Type.GetBindableProperty(propertyName.NamespaceURI, ref localName, out bool attached, context, node as IXmlLineInfo);
var propertySymbol = parentVar.Type.GetAllProperties(localName, context).FirstOrDefault();
var typeandconverter = bpFieldSymbol?.GetBPTypeAndConverter(context);
propertyType = typeandconverter?.type ?? propertySymbol?.Type;
Expand All @@ -573,6 +574,32 @@ internal static bool ProvideValueForAppThemeBindingExtension(ElementNode node, I
if (propertyType is null)
propertyType = context.Compilation.ObjectType;

// Helper to get value from node - handles both ValueNode (direct conversion) and ElementNode (via getNodeValue)
string? GetNodeValueString(INode? targetNode)
{
if (targetNode is null)
return null;

if (targetNode is ValueNode vn)
{
// For ValueNode, convert directly using the property type
if (bpFieldSymbol is not null)
return vn.ConvertTo(bpFieldSymbol, writer, context, context.Variables.TryGetValue(node.Parent, out var pv) ? pv : null);
else if (propertyType is not null)
return vn.ConvertTo(propertyType, writer, context, context.Variables.TryGetValue(node.Parent, out var pv) ? pv : null);
Comment on lines +583 to +589
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable shadowing issue: The variable pv is declared twice within the same scope using the pattern context.Variables.TryGetValue(node.Parent, out var pv). While this compiles, it creates unnecessary variable declarations. Consider declaring pv once before the if-else chain and reusing it.

Suggested change
if (targetNode is ValueNode vn)
{
// For ValueNode, convert directly using the property type
if (bpFieldSymbol is not null)
return vn.ConvertTo(bpFieldSymbol, writer, context, context.Variables.TryGetValue(node.Parent, out var pv) ? pv : null);
else if (propertyType is not null)
return vn.ConvertTo(propertyType, writer, context, context.Variables.TryGetValue(node.Parent, out var pv) ? pv : null);
ILocalValue? pv = null;
if (targetNode is ValueNode vn)
{
// For ValueNode, convert directly using the property type
context.Variables.TryGetValue(node.Parent, out pv);
if (bpFieldSymbol is not null)
return vn.ConvertTo(bpFieldSymbol, writer, context, pv);
else if (propertyType is not null)
return vn.ConvertTo(propertyType, writer, context, pv);

Copilot uses AI. Check for mistakes.
else
return NodeSGExtensions.ValueForLanguagePrimitive(vn.Value as string ?? string.Empty, context.Compilation.ObjectType, context, vn);
}
else if (targetNode is ElementNode)
{
// For ElementNode, use getNodeValue to get the variable reference
var local = getNodeValue(targetNode, propertyType);
return local.ValueAccessor;
}

return null;
}

// Extract Light, Dark, and Default values from the markup extension
string? lightValue = null;
string? darkValue = null;
Expand All @@ -583,24 +610,21 @@ internal static bool ProvideValueForAppThemeBindingExtension(ElementNode node, I
|| node.Properties.TryGetValue(new XmlName(null, "Default"), out defaultNode)
|| (node.CollectionItems.Count == 1 && (defaultNode = node.CollectionItems[0]) != null))
{
var defaultLocal = getNodeValue(defaultNode, propertyType);
defaultValue = defaultLocal.ValueAccessor;
defaultValue = GetNodeValueString(defaultNode);
}

// Check for Light property
if (node.Properties.TryGetValue(new XmlName("", "Light"), out INode? lightNode)
|| node.Properties.TryGetValue(new XmlName(null, "Light"), out lightNode))
{
var lightLocal = getNodeValue(lightNode, propertyType);
lightValue = lightLocal.ValueAccessor;
lightValue = GetNodeValueString(lightNode);
}

// Check for Dark property
if (node.Properties.TryGetValue(new XmlName("", "Dark"), out INode? darkNode)
|| node.Properties.TryGetValue(new XmlName(null, "Dark"), out darkNode))
{
var darkLocal = getNodeValue(darkNode, propertyType);
darkValue = darkLocal.ValueAccessor;
darkValue = GetNodeValueString(darkNode);
}

// At least one value must be set
Expand Down Expand Up @@ -649,22 +673,13 @@ internal static bool ProvideValueForStaticResourceExtension(ElementNode node, In
}

var resource = GetResourceNode(eNode, context, (string)keyValueNode.Value);
if (resource != null && getNodeValue != null)
{
var lvalue = getNodeValue(resource, context.Compilation.ObjectType);
value = lvalue.ValueAccessor;
returnType = lvalue.Type;
return true;
}

if (resource is null || !context.Variables.TryGetValue(resource, out var variable))
{
returnType = context.Compilation.ObjectType;
value = string.Empty;
return false;
}


//if the resource is a string, try to convert it
if (resource.CollectionItems.Count == 1 && resource.CollectionItems[0] is ValueNode vn && vn.Value is string)
{
Expand All @@ -676,6 +691,14 @@ internal static bool ProvideValueForStaticResourceExtension(ElementNode node, In
var typeandconverter = bpFieldSymbol?.GetBPTypeAndConverter(context);

var propertyType = typeandconverter?.type ?? propertySymbol?.Type;
var converter = typeandconverter?.converter;

// If no converter from BP, check the property's TypeConverter attribute
if (converter == null && propertySymbol != null)
{
List<AttributeData> attributes = [.. propertySymbol.GetAttributes(), .. propertyType!.GetAttributes()];
Comment on lines +697 to +699
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential null reference exception: On line 693, propertyType could be null if both typeandconverter?.type and propertySymbol?.Type are null. The check on line 697 only verifies that propertySymbol is not null, but doesn't guarantee that propertyType (i.e., propertySymbol.Type) is not null. Line 699 then uses propertyType!.GetAttributes() which could throw a NullReferenceException. Consider adding a null check for propertyType before accessing its members, e.g., if (converter == null && propertyType != null).

Suggested change
if (converter == null && propertySymbol != null)
{
List<AttributeData> attributes = [.. propertySymbol.GetAttributes(), .. propertyType!.GetAttributes()];
if (converter == null && propertySymbol != null && propertyType != null)
{
List<AttributeData> attributes = [.. propertySymbol.GetAttributes(), .. propertyType.GetAttributes()];

Copilot uses AI. Check for mistakes.
converter = attributes.FirstOrDefault(ad => ad.AttributeClass?.ToString() == "System.ComponentModel.TypeConverterAttribute")?.ConstructorArguments[0].Value as ITypeSymbol;
}

if (propertyType!.Equals(context.Compilation.GetSpecialType(SpecialType.System_String), SymbolEqualityComparer.Default))
{
Expand All @@ -685,7 +708,7 @@ internal static bool ProvideValueForStaticResourceExtension(ElementNode node, In
}
try
{
value = vn.ConvertTo(propertyType!, typeandconverter?.converter, writer, context, parentVar);
value = vn.ConvertTo(propertyType!, converter, writer, context, parentVar);
}
catch (Exception)
{
Expand All @@ -698,6 +721,15 @@ internal static bool ProvideValueForStaticResourceExtension(ElementNode node, In
return true;
}
}

// If we get here and getNodeValue is provided, use it as fallback
if (getNodeValue != null)
{
var lvalue = getNodeValue(resource, context.Compilation.ObjectType);
value = lvalue.ValueAccessor;
returnType = lvalue.Type;
return true;
}

//Fallback to runtime resolution of StaticResource
returnType = context.Compilation.ObjectType;
Expand Down
7 changes: 5 additions & 2 deletions src/Controls/src/SourceGen/NodeSGExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,9 @@ public static void ProvideValue(this ElementNode node, IndentedTextWriter writer
}

public static bool TryProvideValue(this ElementNode node, IndentedTextWriter writer, SourceGenContext context)
=> TryProvideValue(node, writer, context, null);

public static bool TryProvideValue(this ElementNode node, IndentedTextWriter writer, SourceGenContext context, GetNodeValueDelegate? getNodeValue)
{
if (!context.Variables.TryGetValue(node, out var variable))
return false;
Expand All @@ -505,7 +508,7 @@ public static bool TryProvideValue(this ElementNode node, IndentedTextWriter wri
return false;

if (GetKnownLateMarkupExtensions(context).TryGetValue(variable.Type, out var provideValue)
&& provideValue.Invoke(node, writer, context, null, out var returnType0, out var value))
&& provideValue.Invoke(node, writer, context, getNodeValue, out var returnType0, out var value))
{
var variableName = NamingHelpers.CreateUniqueVariableName(context, returnType0 ?? context.Compilation.ObjectType);
context.Writer.WriteLine($"var {variableName} = {value};");
Expand All @@ -516,7 +519,7 @@ public static bool TryProvideValue(this ElementNode node, IndentedTextWriter wri
}

if (GetKnownValueProviders(context).TryGetValue(variable.Type, out provideValue)
&& provideValue.Invoke(node, writer, context, null, out returnType0, out value))
&& provideValue.Invoke(node, writer, context, getNodeValue, out returnType0, out value))
{
var variableName = NamingHelpers.CreateUniqueVariableName(context, returnType0 ?? context.Compilation.ObjectType);
context.Writer.WriteLine($"var {variableName} = {value};");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public void Visit(ElementNode node, INode parentNode)
}

//IMarkupExtension or IValueProvider => ProvideValue()
node.TryProvideValue(Writer, context);
node.TryProvideValue(Writer, context, getNodeValue);

if (propertyName != XmlName.Empty)
{
Expand Down
Loading
Loading