-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[XC] Compile StaticResource when possible
when a resource can be compiled locally, do it. TODO: convert OnPlatform TODO: walk up DataTemplates - fixes #20244
- Loading branch information
1 parent
0548772
commit 2209fd3
Showing
7 changed files
with
296 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
src/Controls/src/Build.Tasks/CompiledMarkupExtensions/StaticResourceExtension.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Xml; | ||
using Microsoft.Maui.Controls.Xaml; | ||
using Mono.Cecil; | ||
using Mono.Cecil.Cil; | ||
using static Mono.Cecil.Cil.Instruction; | ||
using static Mono.Cecil.Cil.OpCodes; | ||
|
||
namespace Microsoft.Maui.Controls.Build.Tasks | ||
{ | ||
//yes, this is a ICompiledMarkupExtension, but declared as ICompiledValueProvider so it's evaluated later (in SetPropertyValue, not CreateObject) | ||
class StaticResourceExtension : ICompiledValueProvider | ||
{ | ||
public IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefref, ModuleDefinition module, BaseNode node, ILContext context) | ||
{ | ||
var name = new XmlName("", "Key"); | ||
var eNode = node as ElementNode; | ||
|
||
if (!eNode.Properties.TryGetValue(name, out INode keyNode) && eNode.CollectionItems.Any()) | ||
keyNode = eNode.CollectionItems[0]; | ||
|
||
if (!(keyNode is ValueNode keyValueNode)) | ||
throw new BuildException(BuildExceptionCode.StaticResourceSyntax, eNode as IXmlLineInfo, null, null); | ||
|
||
var n = eNode; | ||
while (n != null) | ||
{ | ||
if (n.Properties.TryGetValue(new XmlName(XamlParser.MauiUri, "Resources"), out var resourcesNode)) | ||
{ | ||
//single resource in <Resources> | ||
if (resourcesNode is IElementNode irn | ||
&& irn.Properties.TryGetValue(XmlName.xKey, out INode xKeyNode) | ||
&& context.Variables.ContainsKey(irn) | ||
&& xKeyNode is ValueNode xKeyValueNode | ||
&& xKeyValueNode.Value as string == keyValueNode.Value as string) | ||
{ | ||
if (context.Variables[resourcesNode as IElementNode].VariableType.FullName == "System.String") { | ||
foreach (var instruction in TryConvert(irn.CollectionItems[0] as ValueNode, eNode, vardefref, module, context)) | ||
yield return instruction; | ||
yield break; | ||
} | ||
|
||
vardefref.VariableDefinition = context.Variables[irn]; | ||
yield break; | ||
} | ||
//multiple resources in <Resources> | ||
else if (resourcesNode is ListNode lr) { | ||
foreach (var rn in lr.CollectionItems) { | ||
if (rn is IElementNode irn2 | ||
&& irn2.Properties.TryGetValue(XmlName.xKey, out INode xKeyNode2) | ||
&& context.Variables.ContainsKey(irn2) | ||
&& xKeyNode2 is ValueNode xKeyValueNode2 | ||
&& xKeyValueNode2.Value as string == keyValueNode.Value as string) | ||
{ | ||
if (irn2.CollectionItems.Count == 1 && irn2.CollectionItems[0] is ValueNode vn2 && vn2.Value is string) { | ||
foreach (var instruction in TryConvert(vn2, eNode, vardefref, module, context)) | ||
yield return instruction; | ||
yield break; | ||
} | ||
|
||
vardefref.VariableDefinition = context.Variables[irn2]; | ||
yield break; | ||
} | ||
} | ||
} | ||
//explicit ResourceDictionary in Resources | ||
else if (resourcesNode is IElementNode resourceDictionary | ||
&& resourceDictionary.XmlType.Name == "ResourceDictionary") { | ||
foreach (var rn in resourceDictionary.CollectionItems) { | ||
if (rn is IElementNode irn3 | ||
&& irn3.Properties.TryGetValue(XmlName.xKey, out INode xKeyNode3) | ||
&& irn3.XmlType.Name != "OnPlatform" | ||
&& context.Variables.ContainsKey(irn3) | ||
&& xKeyNode3 is ValueNode xKeyValueNode3 | ||
&& xKeyValueNode3.Value as string == keyValueNode.Value as string) | ||
{ | ||
if (irn3.CollectionItems.Count == 1 && irn3.CollectionItems[0] is ValueNode vn3 && vn3.Value is string) { | ||
foreach (var instruction in TryConvert(vn3, eNode, vardefref, module, context)) | ||
yield return instruction; | ||
yield break; | ||
} | ||
|
||
vardefref.VariableDefinition = context.Variables[irn3]; | ||
yield break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
n = n.Parent as ElementNode; | ||
} | ||
|
||
|
||
//Fallback | ||
foreach (var instruction in FallBack(keyValueNode.Value as string, eNode, module, context).ToList()) | ||
yield return instruction; | ||
|
||
var vardef = new VariableDefinition(module.TypeSystem.Object); | ||
yield return Create(Stloc, vardef); | ||
vardefref.VariableDefinition = vardef; | ||
} | ||
|
||
public static IEnumerable<Instruction> TryConvert(ValueNode stringResourceNode, IElementNode node, VariableDefinitionReference vardefref, ModuleDefinition module, ILContext context) | ||
{ | ||
XmlName propertyName = XmlName.Empty; | ||
SetPropertiesVisitor.TryGetPropertyName(node, node.Parent, out propertyName); | ||
var localName = propertyName.LocalName; | ||
var parentType = module.ImportReference((node.Parent as IElementNode).XmlType.GetTypeReference(context.Cache, module, (IXmlLineInfo)node)); | ||
|
||
var bpRef = SetPropertiesVisitor.GetBindablePropertyReference(parentType, propertyName.NamespaceURI, ref localName, out _, context, (IXmlLineInfo)node); | ||
//BindableProperty | ||
if (bpRef != null) | ||
{ | ||
var targetTypeRef = module.ImportReference(bpRef.GetBindablePropertyType(context.Cache, node as IXmlLineInfo, module)); | ||
foreach (var instruction in stringResourceNode.PushConvertedValue(context, bpRef, stringResourceNode.PushServiceProvider(context, bpRef: bpRef), true, false)) | ||
yield return instruction; | ||
var vardef = new VariableDefinition(targetTypeRef); | ||
yield return Create(Stloc, vardef); | ||
vardefref.VariableDefinition = vardef; | ||
yield break; | ||
} | ||
|
||
var propertyRef = parentType.GetProperty(context.Cache, pd => pd.Name == localName, out var declaringTypeReference); | ||
if (propertyRef != null) | ||
{ | ||
foreach (var instruction in stringResourceNode.PushConvertedValue(context, propertyRef.PropertyType, new ICustomAttributeProvider[] { propertyRef, propertyRef.PropertyType.ResolveCached(context.Cache) }, stringResourceNode.PushServiceProvider(context, propertyRef: propertyRef), true, false)) | ||
yield return instruction; | ||
var vardef = new VariableDefinition(propertyRef.PropertyType); | ||
yield return Create(Stloc, vardef); | ||
vardefref.VariableDefinition = vardef; | ||
yield break; | ||
} | ||
|
||
|
||
} | ||
|
||
public static IEnumerable<Instruction> FallBack(string key, IElementNode node, ModuleDefinition module, ILContext context) | ||
{ | ||
var staticResourceExtensionType = module.ImportReference(context.Cache, | ||
("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "StaticResourceExtension")); | ||
yield return Create(Newobj, module.ImportCtorReference(context.Cache, | ||
staticResourceExtensionType, | ||
paramCount: 0)); | ||
|
||
SetPropertiesVisitor.TryGetPropertyName(node, node.Parent, out var propertyName); | ||
var localName = propertyName.LocalName; | ||
|
||
//Set the Key | ||
var keyProperty = staticResourceExtensionType.GetProperty(context.Cache, pd => pd.Name == "Key", out _); | ||
yield return Create(Dup); | ||
yield return Create(Ldstr, key); | ||
yield return Create(Callvirt, module.ImportReference(keyProperty.SetMethod)); | ||
|
||
FieldReference bpRef = null; | ||
PropertyDefinition propertyRef = null; | ||
TypeReference declaringTypeReference = null; | ||
if (node.Parent is IElementNode parentNode && propertyName != XmlName.Empty) | ||
{ | ||
var parentType = module.ImportReference(parentNode.XmlType.GetTypeReference(context.Cache, module, (IXmlLineInfo)node)); | ||
bpRef = SetPropertiesVisitor.GetBindablePropertyReference(parentType, | ||
propertyName.NamespaceURI, | ||
ref localName, | ||
out _, | ||
context, | ||
(IXmlLineInfo)node); | ||
propertyRef = parentType.GetProperty(context.Cache, pd => pd.Name == localName, out declaringTypeReference); | ||
|
||
} | ||
foreach (var instruction in node.PushServiceProvider(context, bpRef, propertyRef, declaringTypeReference)) | ||
yield return instruction; | ||
|
||
yield return Create(Callvirt, module.ImportMethodReference(context.Cache, | ||
("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "StaticResourceExtension"), | ||
methodName: "ProvideValue", | ||
parameterTypes: new[] { ("System.ComponentModel", "System", "IServiceProvider") })); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests" | ||
xmlns:System="clr-namespace:System;assembly=mscorlib" | ||
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui20244"> | ||
<ContentPage.Resources> | ||
<x:String x:Key="GridColDef">*, *, *, *, *, auto</x:String> | ||
</ContentPage.Resources> | ||
<StackLayout> | ||
<StackLayout.Resources> | ||
<ResourceDictionary> | ||
<Color x:Key="reddish">#FF0000</Color> | ||
<x:String x:Key="GridRowDef">*, *, auto</x:String> | ||
</ResourceDictionary> | ||
</StackLayout.Resources> | ||
|
||
<Grid x:Name="grid" RowDefinitions="{StaticResource GridColDef}" ColumnDefinitions="{StaticResource GridRowDef}" /> | ||
</StackLayout> | ||
</ContentPage> |
63 changes: 63 additions & 0 deletions
63
src/Controls/tests/Xaml.UnitTests/Issues/Maui20244.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Globalization; | ||
using System.Linq; | ||
using Microsoft.Maui.ApplicationModel; | ||
using Microsoft.Maui.Controls.Core.UnitTests; | ||
using Microsoft.Maui.Controls.Shapes; | ||
using Microsoft.Maui.Devices; | ||
using Microsoft.Maui.Dispatching; | ||
|
||
using Microsoft.Maui.Graphics; | ||
using Microsoft.Maui.UnitTests; | ||
using NUnit.Framework; | ||
|
||
namespace Microsoft.Maui.Controls.Xaml.UnitTests; | ||
|
||
public partial class Maui20244 :ContentPage | ||
{ | ||
public Maui20244() | ||
{ | ||
InitializeComponent(); | ||
} | ||
|
||
public Maui20244(bool useCompiledXaml) | ||
{ | ||
//this stub will be replaced at compile time | ||
} | ||
|
||
[TestFixture] | ||
class Test | ||
{ | ||
[SetUp] | ||
public void Setup() | ||
{ | ||
Application.SetCurrentApplication(new MockApplication()); | ||
DispatcherProvider.SetCurrent(new DispatcherProviderStub()); | ||
} | ||
|
||
[TearDown] public void TearDown() => AppInfo.SetCurrent(null); | ||
|
||
[Test] | ||
public void RowDefStaticResource([Values(false, true)] bool useCompiledXaml) | ||
{ | ||
if (useCompiledXaml) | ||
MockCompiler.Compile(typeof(Maui20244)); | ||
|
||
var page = new Maui20244(useCompiledXaml); | ||
var grid = page.grid; | ||
|
||
Assert.That(grid.RowDefinitions.Count, Is.EqualTo(6)); | ||
Assert.That(grid.RowDefinitions[0].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star))); | ||
Assert.That(grid.RowDefinitions[1].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star))); | ||
Assert.That(grid.RowDefinitions[2].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star))); | ||
Assert.That(grid.RowDefinitions[3].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star))); | ||
Assert.That(grid.RowDefinitions[4].Height, Is.EqualTo(new GridLength(1, GridUnitType.Star))); | ||
Assert.That(grid.RowDefinitions[5].Height, Is.EqualTo(new GridLength(1, GridUnitType.Auto))); | ||
|
||
Assert.That(grid.ColumnDefinitions.Count, Is.EqualTo(3)); | ||
} | ||
} | ||
|
||
} |