Browse Source

Merge pull request #2322 from AvaloniaUI/xamlil

XAML to IL Compilation
pull/2499/head
danwalmsley 7 years ago
committed by GitHub
parent
commit
9910610573
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitmodules
  2. 6
      build/EmbedXaml.props
  3. 5
      build/SampleApp.props
  4. 6
      dirs.proj
  5. 39
      packages/Avalonia/AvaloniaBuildTasks.targets
  6. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  7. 2
      samples/ControlCatalog/App.xaml
  8. 23
      samples/ControlCatalog/MainView.xaml.cs
  9. 1
      samples/ControlCatalog/MainWindow.xaml
  10. 9
      samples/ControlCatalog/Pages/ButtonPage.xaml.cs
  11. 2
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  12. 2
      samples/ControlCatalog/SideBar.xaml
  13. 21
      src/Avalonia.Base/AvaloniaProperty.cs
  14. 5
      src/Avalonia.Base/Data/Core/ExpressionParseException.cs
  15. 10
      src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs
  16. 10
      src/Avalonia.Base/Platform/IAssetLoader.cs
  17. 30
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  18. 5
      src/Avalonia.Base/Utilities/CharacterReader.cs
  19. 5
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  20. 62
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  21. 73
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  22. 3
      src/Avalonia.Build.Tasks/Extensions.cs
  23. 61
      src/Avalonia.Build.Tasks/Program.cs
  24. 73
      src/Avalonia.Build.Tasks/SpanCompat.cs
  25. 149
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  26. 357
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  27. 6
      src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
  28. 1
      src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
  29. 8
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  30. 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  31. 4
      src/Avalonia.Diagnostics/DevTools.xaml
  32. 6
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  33. 3
      src/Avalonia.Diagnostics/Views/EventsView.xaml
  34. 4
      src/Avalonia.Diagnostics/Views/TreePageView.xaml
  35. 3
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  36. 11
      src/Avalonia.Styling/Styling/Selectors.cs
  37. 2
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  38. 2
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  39. 11
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  40. 9
      src/Avalonia.Themes.Default/DatePicker.xaml
  41. 4
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  42. 7
      src/Avalonia.Themes.Default/DefaultTheme.xaml.cs
  43. 2
      src/Avalonia.Themes.Default/MenuItem.xaml
  44. 2
      src/Avalonia.Visuals/Visual.cs
  45. 32
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  46. 1
      src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
  47. 35
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  48. 4
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  49. 7
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs
  50. 4
      src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs
  51. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs
  52. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs
  53. 61
      src/Markup/Avalonia.Markup.Xaml/Extensions.cs
  54. 11
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  55. 20
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  56. 7
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
  57. 34
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  58. 8
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs
  59. 4
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs
  60. 5
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  61. 7
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  62. 276
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  63. 124
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  64. 176
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  65. 94
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  66. 20
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs
  67. 24
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
  68. 47
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
  69. 71
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
  70. 63
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
  71. 17
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
  72. 338
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  73. 108
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  74. 193
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
  75. 28
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
  76. 53
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  77. 27
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
  78. 36
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs
  79. 186
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  80. 9
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlParentStackProvider.cs
  81. 15
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs
  82. 148
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  83. 1
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  84. 2
      src/Markup/Avalonia.Markup/Data/MultiBinding.cs
  85. 2
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  86. 2
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  87. 7
      src/Shared/PlatformSupport/AssetLoader.cs
  88. 7
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  89. 2
      tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs
  90. 22
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  91. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
  92. 10
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs
  93. 5
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml
  94. 5
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml
  95. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml
  96. 146
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
  97. 23
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs
  98. 5
      tests/Avalonia.UnitTests/MockAssetLoader.cs
  99. 5
      tests/Avalonia.UnitTests/UnitTestApplication.cs

3
.gitmodules

@ -4,3 +4,6 @@
[submodule "nukebuild/Numerge"]
path = nukebuild/Numerge
url = https://github.com/kekekeks/Numerge.git
[submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
url = https://github.com/kekekeks/XamlIl.git

6
build/EmbedXaml.props

@ -4,8 +4,8 @@
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<EmbeddedResource Include="**\*.xaml">
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</AvaloniaResource>
</ItemGroup>
</Project>
</Project>

5
build/SampleApp.props

@ -5,4 +5,9 @@
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
</ItemGroup>
<Target Name="GatherReferences" AfterTargets="CoreCompile">
<WriteLinesToFile File="$(TargetPath).refs"
Lines="@(ReferencePathWithRefAssemblies)"
Overwrite="true" />
</Target>
</Project>

6
dirs.proj

@ -6,12 +6,12 @@
<ProjectReference Include="packages/**/*.*proj" />
<ProjectReference Remove="**/*.shproj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" />
</ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">
<!-- Disabled on CI because of ancient MSBuild project format -->
<ItemGroup>
<ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
</ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\iOS')">
<ProjectReference Remove="src/iOS/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj" />
</ItemGroup>

39
packages/Avalonia/AvaloniaBuildTasks.targets

@ -8,6 +8,10 @@
AssemblyFile="$(AvaloniaBuildTasksLocation)"
/>
<UsingTask TaskName="CompileAvaloniaXamlTask"
AssemblyFile="$(AvaloniaBuildTasksLocation)"
/>
<Target Name="AddAvaloniaResources" BeforeTargets="ResolveReferences">
<PropertyGroup>
@ -20,11 +24,15 @@
</ItemGroup>
</Target>
<PropertyGroup>
<BuildAvaloniaResourcesDependsOn>$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences</BuildAvaloniaResourcesDependsOn>
</PropertyGroup>
<Target Name="GenerateAvaloniaResources"
BeforeTargets="CoreCompile;CoreResGen"
Inputs="@(AvaloniaResource);$(MSBuildAllProjects)"
Outputs="$(AvaloniaResourcesTemporaryFilePath)"
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences">
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
<GenerateAvaloniaResourcesTask
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
Output="$(AvaloniaResourcesTemporaryFilePath)"
@ -33,9 +41,36 @@
EmbeddedResources="@(EmbeddedResources)"/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration)"/>
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
</Target>
<Target
Name="CompileAvaloniaXaml"
AfterTargets="AfterCompile"
Condition="Exists('@(IntermediateAssembly)') And $(DesignTimeBuild) != true And $(EnableAvaloniaXamlCompilation) != false"
>
<PropertyGroup>
<AvaloniaXamlReferencesTemporaryFilePath Condition="'$(AvaloniaXamlReferencesTemporaryFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/references</AvaloniaXamlReferencesTemporaryFilePath>
<AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
</PropertyGroup>
<WriteLinesToFile
Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
File="$(AvaloniaXamlReferencesTemporaryFilePath)"
Lines="@(ReferencePathWithRefAssemblies)"
Overwrite="true" />
<CompileAvaloniaXamlTask
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
AssemblyFile="@(IntermediateAssembly)"
ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)"
OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
ProjectDirectory="$(MSBuildProjectDirectory)"
/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
</Target>
<ItemGroup>
<UpToDateCheckInput Include="@(AvaloniaResource)" />

1
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>
<ItemGroup>

2
samples/ControlCatalog/App.xaml

@ -4,7 +4,7 @@
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
<StyleInclude Source="resm:Avalonia.Controls.DataGrid.Themes.Default.xaml?assembly=Avalonia.Controls.DataGrid"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
<Setter Property="FontWeight" Value="Medium"/>

23
samples/ControlCatalog/MainView.xaml.cs

@ -1,8 +1,11 @@
using System;
using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Markup.Xaml.XamlIl;
using Avalonia.Platform;
using ControlCatalog.Pages;
@ -12,7 +15,7 @@ namespace ControlCatalog
{
public MainView()
{
this.InitializeComponent();
AvaloniaXamlLoader.Load(this);
if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().IsDesktop)
{
IList tabItems = ((IList)this.FindControl<TabControl>("Sidebar").Items);
@ -28,8 +31,17 @@ namespace ControlCatalog
});
}
var light = AvaloniaXamlLoader.Parse<StyleInclude>(@"<StyleInclude xmlns='https://github.com/avaloniaui' Source='resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default'/>");
var dark = AvaloniaXamlLoader.Parse<StyleInclude>(@"<StyleInclude xmlns='https://github.com/avaloniaui' Source='resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default'/>");
var light = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
};
var dark = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
};
var themes = this.Find<ComboBox>("Themes");
themes.SelectionChanged += (sender, e) =>
{
@ -45,10 +57,5 @@ namespace ControlCatalog
};
Styles.Add(light);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

1
samples/ControlCatalog/MainWindow.xaml

@ -1,4 +1,5 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
xmlns:pages="clr-namespace:ControlCatalog.Pages"
Title="Avalonia Control Gallery"
Icon="/Assets/test_icon.ico"
xmlns:local="clr-namespace:ControlCatalog"

9
samples/ControlCatalog/Pages/ButtonPage.xaml.cs

@ -5,14 +5,5 @@ namespace ControlCatalog.Pages
{
public class ButtonPage : UserControl
{
public ButtonPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

2
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
private void OnSpin(object sender, SpinEventArgs e)
public void OnSpin(object sender, SpinEventArgs e)
{
var spinner = (ButtonSpinner)sender;
var txtBox = (TextBlock)spinner.Content;

2
samples/ControlCatalog/SideBar.xaml

@ -1,6 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.SideBar">
>
<Design.PreviewWith>
<Border Padding="20">
<TabControl Classes="sidebar">

21
src/Avalonia.Base/AvaloniaProperty.cs

@ -19,7 +19,7 @@ namespace Avalonia
/// <summary>
/// Represents an unset property value.
/// </summary>
public static readonly object UnsetValue = new Unset();
public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _initialized;
@ -546,16 +546,17 @@ namespace Avalonia
}
}
}
/// <summary>
/// Class representing the <see cref="AvaloniaProperty.UnsetValue"/>.
/// </summary>
public class UnsetValueType
{
/// <summary>
/// Class representing the <see cref="UnsetValue"/>.
/// Returns the string representation of the <see cref="AvaloniaProperty.UnsetValue"/>.
/// </summary>
private class Unset
{
/// <summary>
/// Returns the string representation of the <see cref="UnsetValue"/>.
/// </summary>
/// <returns>The string "(unset)".</returns>
public override string ToString() => "(unset)";
}
/// <returns>The string "(unset)".</returns>
public override string ToString() => "(unset)";
}
}

5
src/Avalonia.Base/Data/Core/ExpressionParseException.cs

@ -9,7 +9,10 @@ namespace Avalonia.Data.Core
/// Exception thrown when <see cref="ExpressionObserver"/> could not parse the provided
/// expression string.
/// </summary>
public class ExpressionParseException : Exception
#if !BUILDTASK
public
#endif
class ExpressionParseException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionParseException"/> class.

10
src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs

@ -0,0 +1,10 @@
using System;
namespace Avalonia.Metadata
{
[AttributeUsage(AttributeTargets.Class)]
public class UsableDuringInitializationAttribute : Attribute
{
}
}

10
src/Avalonia.Base/Platform/IAssetLoader.cs

@ -61,6 +61,16 @@ namespace Avalonia.Platform
/// </exception>
(Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null);
/// <summary>
/// Extracts assembly information from URI
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>Assembly associated with the Uri</returns>
Assembly GetAssembly(Uri uri, Uri baseUri = null);
/// <summary>
/// Gets all assets of a folder and subfolders that match specified uri.
/// </summary>

30
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@ -46,6 +46,36 @@ namespace Avalonia.Utilities
Entries = entries
});
}
public static byte[] Create(Dictionary<string, byte[]> data)
{
var sources = data.ToList();
var offsets = new Dictionary<string, int>();
var coffset = 0;
foreach (var s in sources)
{
offsets[s.Key] = coffset;
coffset += s.Value.Length;
}
var index = sources.Select(s => new AvaloniaResourcesIndexEntry
{
Path = s.Key,
Size = s.Value.Length,
Offset = offsets[s.Key]
}).ToList();
var output = new MemoryStream();
var ms = new MemoryStream();
AvaloniaResourcesIndexReaderWriter.Write(ms, index);
new BinaryWriter(output).Write((int)ms.Length);
ms.Position = 0;
ms.CopyTo(output);
foreach (var s in sources)
{
output.Write(s.Value,0,s.Value.Length);
}
return output.ToArray();
}
}
[DataContract]

5
src/Avalonia.Base/Utilities/CharacterReader.cs

@ -5,7 +5,10 @@ using System;
namespace Avalonia.Utilities
{
public ref struct CharacterReader
#if !BUILDTASK
public
#endif
ref struct CharacterReader
{
private ReadOnlySpan<char> _s;

5
src/Avalonia.Base/Utilities/IdentifierParser.cs

@ -6,7 +6,10 @@ using System.Globalization;
namespace Avalonia.Utilities
{
public static class IdentifierParser
#if !BUILDTASK
public
#endif
static class IdentifierParser
{
public static ReadOnlySpan<char> ParseIdentifier(this ref CharacterReader r)
{

62
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -1,9 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition="$(Configuration) == 'Debug'">netstandard2.0;netcoreapp2.0</TargetFrameworks>
<OutputType>exe</OutputType>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
<DefineConstants>$(DefineConstants);BUILDTASK</DefineConstants>
<DefineConstants>$(DefineConstants);BUILDTASK;XAMLIL_CECIL_INTERNAL;XAMLIL_INTERNAL</DefineConstants>
<CopyLocalLockFileAssemblies Condition="$(TargetFramework) == 'netstandard2.0'">true</CopyLocalLockFileAssemblies>
<NoWarn>NU1605</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -13,6 +17,54 @@
<Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs">
<Link>Shared/AvaloniaResourceXamlInfo.cs</Link>
</Compile>
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" />
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/**/*.cs">
<Link>XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Remove="external/cecil/**/*.*" />
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\**\*.cs">
<Link>XamlIl/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs">
<Link>XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Data/Core/ExpressionParseException.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/CharacterReader.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/IdentifierParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\TypeSystem\SreTypeSystem.cs" />
<PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />
<PackageReference Condition="$(TargetFramework) == 'netstandard2.0'" Include="ILRepack.MSBuild.Task" Version="2.0.13" PrivateAssets="All" />
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
</ItemGroup>
<Target Name="ILRepack" AfterTargets="Build" Condition="$(TargetFramework) == 'netstandard2.0'">
<PropertyGroup>
<WorkingDirectory>$(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)</WorkingDirectory>
</PropertyGroup>
<ItemGroup>
<InputAssemblies Include="Mono.Cecil.dll" />
</ItemGroup>
<ILRepack OutputType="$(OutputType)" MainAssembly="$(AssemblyName).dll" OutputAssembly="$(AssemblyName).dll" InputAssemblies="@(InputAssemblies)" WorkingDirectory="$(WorkingDirectory)" />
<ItemGroup>
<DeleteNonNeededResults Include="$(WorkingDirectory)\*.dll" />
<DeleteNonNeededResults Remove="$(WorkingDirectory)\Avalonia.Build.Tasks.dll" />
</ItemGroup>
<Delete Files="@(DeleteNonNeededResults)" />
</Target>
</Project>

73
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@ -0,0 +1,73 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks
{
public class CompileAvaloniaXamlTask: ITask
{
public bool Execute()
{
OutputPath = OutputPath ?? AssemblyFile;
var outputPdb = GetPdbPath(OutputPath);
var input = AssemblyFile;
var inputPdb = GetPdbPath(input);
// Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
if (OriginalCopyPath != null)
{
File.Copy(AssemblyFile, OriginalCopyPath, true);
input = OriginalCopyPath;
File.Delete(AssemblyFile);
if (File.Exists(inputPdb))
{
var copyPdb = GetPdbPath(OriginalCopyPath);
File.Copy(inputPdb, copyPdb, true);
File.Delete(inputPdb);
inputPdb = copyPdb;
}
}
var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, OutputPath);
if (!res.Success)
return false;
if (!res.WrittenFile)
{
File.Copy(input, OutputPath, true);
if(File.Exists(inputPdb))
File.Copy(inputPdb, outputPdb, true);
}
return true;
}
string GetPdbPath(string p)
{
var d = Path.GetDirectoryName(p);
var f = Path.GetFileNameWithoutExtension(p);
var rv = f + ".pdb";
if (d != null)
rv = Path.Combine(d, rv);
return rv;
}
[Required]
public string AssemblyFile { get; set; }
[Required]
public string ReferencesFilePath { get; set; }
[Required]
public string OriginalCopyPath { get; set; }
[Required]
public string ProjectDirectory { get; set; }
public string OutputPath { get; set; }
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
}
}

3
src/Avalonia.Build.Tasks/Extensions.cs

@ -1,8 +1,9 @@
using System;
using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks
{
public static class Extensions
static class Extensions
{
static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}";

61
src/Avalonia.Build.Tasks/Program.cs

@ -0,0 +1,61 @@
using System;
using System.Collections;
using System.IO;
using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks
{
public class Program
{
static int Main(string[] args)
{
if (args.Length != 3)
{
Console.Error.WriteLine("input references output");
return 1;
}
return new CompileAvaloniaXamlTask()
{
AssemblyFile = args[0],
ReferencesFilePath = args[1],
OutputPath = args[2],
BuildEngine = new ConsoleBuildEngine(),
ProjectDirectory = Directory.GetCurrentDirectory()
}.Execute() ?
0 :
2;
}
class ConsoleBuildEngine : IBuildEngine
{
public void LogErrorEvent(BuildErrorEventArgs e)
{
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
}
public void LogWarningEvent(BuildWarningEventArgs e)
{
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
}
public void LogMessageEvent(BuildMessageEventArgs e)
{
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
}
public void LogCustomEvent(CustomBuildEventArgs e)
{
Console.WriteLine($"CUSTOM: {e.Message}");
}
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
IDictionary targetOutputs) => throw new NotSupportedException();
public bool ContinueOnError { get; }
public int LineNumberOfTaskNode { get; }
public int ColumnNumberOfTaskNode { get; }
public string ProjectFileOfTaskNode { get; }
}
}
}

73
src/Avalonia.Build.Tasks/SpanCompat.cs

@ -0,0 +1,73 @@
namespace System
{
// This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory
struct ReadOnlySpan<T>
{
private string _s;
private int _start;
private int _length;
public int Length => _length;
public ReadOnlySpan(string s) : this(s, 0, s.Length)
{
}
public ReadOnlySpan(string s, int start, int len)
{
_s = s;
_length = len;
_start = start;
if (_start > s.Length)
_length = 0;
else if (_start + _length > s.Length)
_length = s.Length - _start;
}
public char this[int c] => _s[_start + c];
public bool IsEmpty => _length == 0;
public ReadOnlySpan<char> Slice(int start, int len)
{
return new ReadOnlySpan<char>(_s, _start + start, len);
}
public static ReadOnlySpan<char> Empty => default;
public ReadOnlySpan<char> Slice(int start)
{
return new ReadOnlySpan<char>(_s, _start + start, _length - start);
}
public bool SequenceEqual(ReadOnlySpan<char> other)
{
if (_length != other.Length)
return false;
for(var c=0; c<_length;c++)
if (this[c] != other[c])
return false;
return true;
}
public ReadOnlySpan<char> TrimStart()
{
int start = 0;
for (; start < Length; start++)
{
if (!char.IsWhiteSpace(this[start]))
{
break;
}
}
return Slice(start);
}
public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length);
}
static class SpanCompatExtensions
{
public static ReadOnlySpan<char> AsSpan(this string s) => new ReadOnlySpan<char>(s);
}
}

149
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs

@ -0,0 +1,149 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Utilities;
using Mono.Cecil;
using Mono.Cecil.Cil;
using XamlIl.TypeSystem;
namespace Avalonia.Build.Tasks
{
public static partial class XamlCompilerTaskExecutor
{
interface IResource : IFileSource
{
string Uri { get; }
string Name { get; }
void Remove();
}
interface IResourceGroup
{
string Name { get; }
IEnumerable<IResource> Resources { get; }
}
class EmbeddedResources : IResourceGroup
{
private readonly AssemblyDefinition _asm;
public string Name => "EmbeddedResource";
public IEnumerable<IResource> Resources => _asm.MainModule.Resources.OfType<EmbeddedResource>()
.Select(r => new WrappedResource(_asm, r)).ToList();
public EmbeddedResources(AssemblyDefinition asm)
{
_asm = asm;
}
class WrappedResource : IResource
{
private readonly AssemblyDefinition _asm;
private readonly EmbeddedResource _res;
public WrappedResource(AssemblyDefinition asm, EmbeddedResource res)
{
_asm = asm;
_res = res;
}
public string Uri => $"resm:{Name}?assembly={_asm.Name.Name}";
public string Name => _res.Name;
public string FilePath => Name;
public byte[] FileContents => _res.GetResourceData();
public void Remove() => _asm.MainModule.Resources.Remove(_res);
}
}
class AvaloniaResources : IResourceGroup
{
private readonly AssemblyDefinition _asm;
Dictionary<string, AvaloniaResource> _resources = new Dictionary<string, AvaloniaResource>();
private EmbeddedResource _embedded;
public AvaloniaResources(AssemblyDefinition asm, string projectDir)
{
_asm = asm;
_embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r =>
r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources"));
if (_embedded == null)
return;
using (var stream = _embedded.GetResourceStream())
{
var br = new BinaryReader(stream);
var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32())));
var baseOffset = stream.Position;
foreach (var e in index)
{
stream.Position = e.Offset + baseOffset;
_resources[e.Path] = new AvaloniaResource(this, projectDir, e.Path, br.ReadBytes(e.Size));
}
}
}
public void Save()
{
if (_embedded != null)
{
_asm.MainModule.Resources.Remove(_embedded);
_embedded = null;
}
if (_resources.Count == 0)
return;
_embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public,
AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key,
x => x.Value.FileContents)));
_asm.MainModule.Resources.Add(_embedded);
}
public string Name => "AvaloniaResources";
public IEnumerable<IResource> Resources => _resources.Values.ToList();
class AvaloniaResource : IResource
{
private readonly AvaloniaResources _grp;
private readonly byte[] _data;
public AvaloniaResource(AvaloniaResources grp,
string projectDir,
string name, byte[] data)
{
_grp = grp;
_data = data;
Name = name;
FilePath = Path.Combine(projectDir, name.TrimStart('/'));
Uri = $"avares://{grp._asm.Name.Name}/{name.TrimStart('/')}";
}
public string Uri { get; }
public string Name { get; }
public string FilePath { get; }
public byte[] FileContents => _data;
public void Remove() => _grp._resources.Remove(Name);
}
}
static void CopyDebugDocument(MethodDefinition method, MethodDefinition copyFrom)
{
if (!copyFrom.DebugInformation.HasSequencePoints)
return;
var dbg = method.DebugInformation;
dbg.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.First())
{
End = new InstructionOffset(),
Import = new ImportDebugInformation()
};
dbg.SequencePoints.Add(new SequencePoint(method.Body.Instructions.First(),
copyFrom.DebugInformation.SequencePoints.First().Document)
{
StartLine = 0xfeefee,
EndLine = 0xfeefee
});
}
}
}

357
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -0,0 +1,357 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework;
using Mono.Cecil;
using XamlIl.TypeSystem;
using Avalonia.Utilities;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Parsers;
using XamlIl.Transform;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using TypeAttributes = Mono.Cecil.TypeAttributes;
namespace Avalonia.Build.Tasks
{
public static partial class XamlCompilerTaskExecutor
{
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
|| r.Name.ToLowerInvariant().EndsWith(".paml");
public class CompileResult
{
public bool Success { get; set; }
public bool WrittenFile { get; }
public CompileResult(bool success, bool writtenFile = false)
{
Success = success;
WrittenFile = writtenFile;
}
}
public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory,
string output)
{
var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input);
var asm = typeSystem.TargetAssemblyDefinition;
var emres = new EmbeddedResources(asm);
var avares = new AvaloniaResources(asm, projectDirectory);
if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
// Nothing to do
return new CompileResult(true);
var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new XamlIlTransformerConfiguration(typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
AvaloniaXamlIlLanguage.CustomValueConverter);
var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(contextDef);
var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
xamlLanguage);
var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass);
var editorBrowsableAttribute = typeSystem
.GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute"))
.Resolve();
var editorBrowsableCtor =
asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors()
.First(c => c.Parameters.Count == 1));
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var rootServiceProviderField = asm.MainModule.ImportReference(
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Fields
.First(x => x.Name == "RootServiceProviderV1"));
var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
{
ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
});
var loaderDispatcherMethod = new MethodDefinition("TryLoad",
MethodAttributes.Static | MethodAttributes.Public,
asm.MainModule.TypeSystem.Object)
{
Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)}
};
loaderDispatcherDef.Methods.Add(loaderDispatcherMethod);
asm.MainModule.Types.Add(loaderDispatcherDef);
var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First(
m =>
m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 2 &&
m.ReturnType.FullName == "System.Boolean"
&& m.Parameters[0].ParameterType.FullName == "System.String"
&& m.Parameters[1].ParameterType.FullName == "System.String"));
bool CompileGroup(IResourceGroup group)
{
var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name,
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
{
ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
});
asm.MainModule.Types.Add(typeDef);
var builder = typeSystem.CreateTypeBuilder(typeDef);
foreach (var res in group.Resources.Where(CheckXamlName))
{
try
{
// StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlIlParser.Parse(xaml);
var initialRoot = (XamlIlAstObjectNode)parsed.Root;
var precompileDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile");
if (precompileDirective != null)
{
var precompileText = (precompileDirective.Values[0] as XamlIlAstTextNode)?.Text.Trim()
.ToLowerInvariant();
if (precompileText == "false")
continue;
if (precompileText != "true")
throw new XamlIlParseException("Invalid value for x:Precompile", precompileDirective);
}
var classDirective = initialRoot.Children.OfType<XamlIlAstXmlDirective>()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
IXamlIlType classType = null;
if (classDirective != null)
{
if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn))
throw new XamlIlParseException("x:Class should have a string value", classDirective);
classType = typeSystem.TargetAssembly.FindType(tn.Text);
if (classType == null)
throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective);
initialRoot.Type = new XamlIlAstClrTypeReference(classDirective, classType, false);
initialRoot.Children.Remove(classDirective);
}
compiler.Transform(parsed);
var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
var buildName = classType == null ? "Build:" + res.Name : null;
var classTypeDefinition =
classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
var populateBuilder = classTypeDefinition == null ?
builder :
typeSystem.CreateTypeBuilder(classTypeDefinition);
compiler.Compile(parsed, contextClass,
compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
classTypeDefinition == null),
buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true),
builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name,
true),
(closureName, closureBaseType) =>
populateBuilder.DefineSubType(closureBaseType, closureName, false),
res.Uri, res
);
if (classTypeDefinition != null)
{
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
.Methods.First(m => m.Name == populateName);
var designLoaderFieldType = typeSystem
.GetType("System.Action`1")
.MakeGenericType(typeSystem.GetType("System.Object"));
var designLoaderFieldTypeReference = (GenericInstanceType)typeSystem.GetTypeReference(designLoaderFieldType);
designLoaderFieldTypeReference.GenericArguments[0] =
asm.MainModule.ImportReference(designLoaderFieldTypeReference.GenericArguments[0]);
designLoaderFieldTypeReference = (GenericInstanceType)
asm.MainModule.ImportReference(designLoaderFieldTypeReference);
var designLoaderLoad =
typeSystem.GetMethodReference(
designLoaderFieldType.Methods.First(m => m.Name == "Invoke"));
designLoaderLoad =
asm.MainModule.ImportReference(designLoaderLoad);
designLoaderLoad.DeclaringType = designLoaderFieldTypeReference;
var designLoaderField = new FieldDefinition("!XamlIlPopulateOverride",
FieldAttributes.Static | FieldAttributes.Private, designLoaderFieldTypeReference);
classTypeDefinition.Fields.Add(designLoaderField);
const string TrampolineName = "!XamlIlPopulateTrampoline";
var trampoline = new MethodDefinition(TrampolineName,
MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
classTypeDefinition.Methods.Add(trampoline);
var regularStart = Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField);
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
trampoline.Body.Instructions.Add(regularStart);
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
CopyDebugDocument(trampoline, compiledPopulateMethod);
var foundXamlLoader = false;
// Find AvaloniaXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
foreach (var method in classTypeDefinition.Methods
.Where(m => !m.Attributes.HasFlag(MethodAttributes.Static)))
{
var i = method.Body.Instructions;
for (var c = 1; c < i.Count; c++)
{
if (i[c - 1].OpCode == OpCodes.Ldarg_0
&& i[c].OpCode == OpCodes.Call)
{
var op = i[c].Operand as MethodReference;
// TODO: Throw an error
// This usually happens when same XAML resource was added twice for some weird reason
// We currently support it for dual-named default theme resource
if (op != null
&& op.Name == TrampolineName)
{
foundXamlLoader = true;
break;
}
if (op != null
&& op.Name == "Load"
&& op.Parameters.Count == 1
&& op.Parameters[0].ParameterType.FullName == "System.Object"
&& op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader")
{
i[c].Operand = trampoline;
foundXamlLoader = true;
}
}
}
}
if (!foundXamlLoader)
{
var ctors = classTypeDefinition.GetConstructors()
.Where(c => !c.IsStatic).ToList();
// We can inject xaml loader into default constructor
if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3)
{
var i = ctors[0].Body.Instructions;
var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline));
i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
}
else
{
throw new InvalidProgramException(
$"No call to AvaloniaXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
}
}
}
if (buildName != null || classTypeDefinition != null)
{
var compiledBuildMethod = buildName == null ?
null :
typeSystem.GetTypeReference(builder).Resolve()
.Methods.First(m => m.Name == buildName);
var parameterlessConstructor = compiledBuildMethod != null ?
null :
classTypeDefinition.GetConstructors().FirstOrDefault(c =>
c.IsPublic && !c.IsStatic && !c.HasParameters);
if (compiledBuildMethod != null || parameterlessConstructor != null)
{
var i = loaderDispatcherMethod.Body.Instructions;
var nop = Instruction.Create(OpCodes.Nop);
i.Add(Instruction.Create(OpCodes.Ldarg_0));
i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri));
i.Add(Instruction.Create(OpCodes.Call, stringEquals));
i.Add(Instruction.Create(OpCodes.Brfalse, nop));
if (parameterlessConstructor != null)
i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor));
else
{
i.Add(Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField));
i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
}
i.Add(Instruction.Create(OpCodes.Ret));
i.Add(nop);
}
}
}
catch (Exception e)
{
int lineNumber = 0, linePosition = 0;
if (e is XamlIlParseException xe)
{
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;
}
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", res.FilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
return false;
}
res.Remove();
}
return true;
}
if (emres.Resources.Count(CheckXamlName) != 0)
if (!CompileGroup(emres))
return new CompileResult(false);
if (avares.Resources.Count(CheckXamlName) != 0)
{
if (!CompileGroup(avares))
return new CompileResult(false);
avares.Save();
}
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
asm.Write(output, new WriterParameters
{
WriteSymbols = asm.MainModule.HasSymbols
});
return new CompileResult(true, true);
}
}
}

6
src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
@ -11,10 +11,14 @@
<ProjectReference Include="..\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<!-- Compatibility with old apps -->
<EmbeddedResource Include="Themes\**\*.xaml"/>
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

1
src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs

@ -26,6 +26,7 @@ namespace Avalonia.Controls
/// Gets or sets the binding that associates the column with a property in the data source.
/// </summary>
//TODO Binding
[AssignBinding]
public virtual IBinding Binding
{
get

8
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Xml;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Remote.Protocol;
@ -206,7 +207,8 @@ namespace Avalonia.DesignerSupport.Remote
catch (Exception e)
{
var xamlException = e as XamlException;
var xmlException = e as XmlException;
s_transport.Send(new UpdateXamlResultMessage
{
Error = e.ToString(),
@ -214,8 +216,8 @@ namespace Avalonia.DesignerSupport.Remote
{
ExceptionType = e.GetType().FullName,
Message = e.Message.ToString(),
LineNumber = xamlException?.LineNumber,
LinePosition = xamlException?.LinePosition,
LineNumber = xamlException?.LineNumber ?? xmlException?.LineNumber,
LinePosition = xamlException?.LinePosition ?? xmlException?.LinePosition,
}
});
}

1
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -17,4 +17,5 @@
</ItemGroup>
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

4
src/Avalonia.Diagnostics/DevTools.xaml

@ -1,4 +1,6 @@
<UserControl xmlns="https://github.com/avaloniaui">
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Avalonia.Diagnostics.DevTools">
<Grid RowDefinitions="Auto,*,Auto">
<TabStrip SelectedIndex="{Binding SelectedTab, Mode=TwoWay}">
<TabStripItem Content="Logical Tree"/>

6
src/Avalonia.Diagnostics/DevTools.xaml.cs

@ -43,6 +43,12 @@ namespace Avalonia.Diagnostics
.Subscribe(RawKeyDown);
}
// HACK: needed for XAMLIL, will fix that later
public DevTools()
{
}
public IControl Root { get; }
public static IDisposable Attach(TopLevel control)

3
src/Avalonia.Diagnostics/Views/EventsView.xaml

@ -1,6 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels">
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
x:Class="Avalonia.Diagnostics.Views.EventsView">
<UserControl.Resources>
<vm:BoolToBrushConverter x:Key="boolToBrush" />
</UserControl.Resources>

4
src/Avalonia.Diagnostics/Views/TreePageView.xaml

@ -1,5 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels">
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Avalonia.Diagnostics.Views.TreePageView">
<Grid ColumnDefinitions="*,4,3*">
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
<TreeView.DataTemplates>

3
src/Avalonia.Styling/Controls/NameScopeExtensions.cs

@ -71,10 +71,11 @@ namespace Avalonia.Controls
{
Contract.Requires<ArgumentNullException>(control != null);
return control.GetSelfAndLogicalAncestors()
var scope = control.GetSelfAndLogicalAncestors()
.OfType<StyledElement>()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
.FirstOrDefault(x => x != null);
return scope;
}
}
}

11
src/Avalonia.Styling/Styling/Selectors.cs

@ -114,6 +114,17 @@ namespace Avalonia.Styling
{
return new NotSelector(previous, argument(null));
}
/// <summary>
/// Returns a selector which inverts the results of selector argument.
/// </summary>
/// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param>
/// <returns>The selector.</returns>
public static Selector Not(this Selector previous, Selector argument)
{
return new NotSelector(previous, argument);
}
/// <summary>
/// Returns a selector which matches a type.

2
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@ -1,6 +1,6 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<Color x:Key="ThemeAccentColor">#CC119EDA</Color>

2
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -1,6 +1,6 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<Color x:Key="ThemeAccentColor">#CC119EDA</Color>

11
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@ -12,13 +12,12 @@
<ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
<AvaloniaResource Include="DefaultTheme.xaml" />
<AvaloniaResource Include="Accents/*.xaml" />
<AvaloniaResource Include="DefaultTheme.xaml"/>
<AvaloniaResource Include="Accents/*.xaml"/>
<!-- Compatibility with old apps, probably need to replace with AvaloniaResource -->
<EmbeddedResource Include="**/*.xaml"/>
</ItemGroup>
<ItemGroup>
<None Remove="NotificationArea.xaml" />
</ItemGroup>
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\BuildTargets.targets"/>
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\Rx.props" />
</Project>

9
src/Avalonia.Themes.Default/DatePicker.xaml

@ -7,7 +7,7 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style Selector="DatePicker">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
@ -63,11 +63,8 @@
Grid.ColumnSpan="4"
Grid.RowSpan="3"
FontSize="{DynamicResource FontSizeSmall}"
Foreground="{DynamicResource ThemeBorderHighBrush}">
<TextBlock.Text>
<Binding Source="{x:Static sys:DateTime.Today}" Path="Day"/>
</TextBlock.Text>
</TextBlock>
Foreground="{DynamicResource ThemeBorderHighBrush}"
Text="{Binding Source={x:Static sys:DateTime.Today}, Path=Day}"/>
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Center" Fill="{DynamicResource ThemeControlLowBrush}" StrokeThickness="0" Grid.ColumnSpan="4" Width="3" Height="3"/>
</Grid>

4
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -1,4 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Avalonia.Themes.Default.DefaultTheme">
<!-- Define ToolTip first so its styles can be overriden by other controls (e.g. TextBox) -->
<StyleInclude Source="resm:Avalonia.Themes.Default.ToolTip.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.DataValidationErrors.xaml?assembly=Avalonia.Themes.Default"/>

7
src/Avalonia.Themes.Default/DefaultTheme.xaml.cs

@ -11,12 +11,5 @@ namespace Avalonia.Themes.Default
/// </summary>
public class DefaultTheme : Styles
{
/// <summary>
/// Initializes a new instance of the <see cref="DefaultTheme"/> class.
/// </summary>
public DefaultTheme()
{
AvaloniaXamlLoader.Load(this);
}
}
}

2
src/Avalonia.Themes.Default/MenuItem.xaml

@ -1,5 +1,5 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style Selector="MenuItem">
<Setter Property="Background" Value="Transparent"/>

2
src/Avalonia.Visuals/Visual.cs

@ -10,6 +10,7 @@ using Avalonia.Data;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.Utilities;
using Avalonia.VisualTree;
@ -25,6 +26,7 @@ namespace Avalonia
/// <see cref="IRenderer"/> to render the control. To traverse the visual tree, use the
/// extension methods defined in <see cref="VisualExtensions"/>.
/// </remarks>
[UsableDuringInitialization]
public class Visual : StyledElement, IVisual
{
/// <summary>

32
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -1,7 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER</DefineConstants>
<DefineConstants>PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLIL_INTERNAL</DefineConstants>
<UseCecil>false</UseCecil>
<DefineConstants Condition="$(UseCecil) == true">$(DefineConstants);RUNTIME_XAML_CECIL</DefineConstants>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<EnableDefaultItems>false</EnableDefaultItems>
<NoWarn>CS1591</NoWarn>
@ -16,6 +18,7 @@
<Compile Include="Converters\ParseTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" />
<Compile Include="Converters\TimeSpanTypeConverter.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
<Compile Include="MarkupExtensions\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
@ -51,8 +54,33 @@
<Compile Include="Templates\TemplateContent.cs" />
<Compile Include="Templates\TemplateLoader.cs" />
<Compile Include="Templates\TreeDataTemplate.cs" />
<Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionHackTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlAvaloniaPropertyResolver.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlConstructorServiceProviderTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlDesignPropertiesTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlMetadataRemover.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformInstanceAttachedProperties.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlWellKnownTypes.cs" />
<Compile Include="XamlIl\CompilerExtensions\XamlIlAvaloniaPropertyHelper.cs" />
<Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlCompiler.cs" />
<Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlLanguage.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AddNameScopeRegistration.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlSetterTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\IgnoredDirectivesTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlSelectorTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\XNameTransformer.cs" />
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlParentStackProvider.cs" />
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
<Compile Include="XamlLoadException.cs" />
<Compile Include="PortableXaml\portable.xaml.github\src\Portable.Xaml\**\*.cs" Exclude="PortableXaml\portable.xaml.github\src\Portable.Xaml\Assembly\**\*.cs" />
<Compile Include="XamlIl\xamlil.github\src\XamlIl\**\*.cs" />
<Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs" />
<Compile Remove="XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Include="..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />
@ -64,6 +92,8 @@
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\Avalonia.Markup\Avalonia.Markup.csproj" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
<PackageReference Condition="$(UseCecil) == true" Include="Mono.Cecil" Version="0.10.3" />
</ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
</Project>

1
src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs

@ -30,6 +30,7 @@ namespace Avalonia.Markup.Xaml
/// </remarks>
public static class AvaloniaTypeConverters
{
// When adding item to that list make sure to modify AvaloniaXamlIlLanguage
private static Dictionary<Type, Type> _converters = new Dictionary<Type, Type>()
{
{ typeof(AvaloniaList<>), typeof(AvaloniaListConverter<>) },

35
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -10,6 +10,7 @@ using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Linq;
using Avalonia.Markup.Xaml.XamlIl;
using Avalonia.Controls;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.PortableXaml;
@ -25,6 +26,8 @@ namespace Avalonia.Markup.Xaml
{
public bool IsDesignMode { get; set; }
public static bool UseLegacyXamlLoader { get; set; } = false;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
/// </summary>
@ -65,8 +68,8 @@ namespace Avalonia.Markup.Xaml
{
throw new InvalidOperationException(
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
}
foreach (var uri in GetUrisFor(assetLocator, type))
{
if (assetLocator.Exists(uri))
@ -113,21 +116,27 @@ namespace Avalonia.Markup.Xaml
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
var compiledLoader = assetLocator.GetAssembly(uri, baseUri)
?.GetType("CompiledAvaloniaXaml.!XamlLoader")
?.GetMethod("TryLoad", new[] {typeof(string)});
if (compiledLoader != null)
{
var uriString = (!uri.IsAbsoluteUri && baseUri != null ? new Uri(baseUri, uri) : uri)
.ToString();
var compiledResult = compiledLoader.Invoke(null, new object[] {uriString});
if (compiledResult != null)
return compiledResult;
}
var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
using (var stream = asset.stream)
{
var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
try
{
return Load(stream, asset.assembly, rootInstance, absoluteUri);
}
catch (Exception e)
{
throw new XamlLoadException("Error loading xaml at " + absoluteUri + ": " + e.Message, e);
}
return Load(stream, asset.assembly, rootInstance, absoluteUri);
}
}
/// <summary>
/// Loads XAML from a string.
/// </summary>
@ -159,6 +168,10 @@ namespace Avalonia.Markup.Xaml
/// <returns>The loaded object.</returns>
public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null)
{
if (!UseLegacyXamlLoader)
return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, IsDesignMode);
var readerSettings = new XamlXmlReaderSettings()
{
BaseUri = uri,

4
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@ -28,8 +28,8 @@ namespace Avalonia.Markup.Xaml.Converters
var parser = new PropertyParser();
var (ns, owner, propertyName) = parser.Parse(new CharacterReader(((string)value).AsSpan()));
var ownerType = TryResolveOwnerByName(context, ns, owner);
var targetType = context.GetFirstAmbientValue<ControlTemplate>()?.TargetType ??
context.GetFirstAmbientValue<Style>()?.Selector?.TargetType ??
var targetType = context.GetFirstParent<ControlTemplate>()?.TargetType ??
context.GetFirstParent<Style>()?.Selector?.TargetType ??
typeof(Control);
var effectiveOwner = ownerType ?? targetType;
var property = registry.FindRegistered(effectiveOwner, propertyName);

7
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs

@ -17,9 +17,10 @@ namespace Avalonia.Markup.Xaml.Converters
if (s == null)
return null;
//On Unix Uri tries to interpret paths starting with "/" as file Uris
if (s.StartsWith("/"))
return new Uri(s, UriKind.Relative);
return new Uri(s);
var kind = s.StartsWith("/") ? UriKind.Relative : UriKind.Absolute;
if (!Uri.TryCreate(s, kind, out var res))
throw new ArgumentException("Unable to parse URI: " + s);
return res;
}
}
}

4
src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs

@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.Converters
return new Bitmap(uri.LocalPath);
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
return new Bitmap(assets.Open(uri, context.GetBaseUri()));
return new Bitmap(assets.Open(uri, context.GetContextBaseUri()));
}
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs

@ -22,7 +22,7 @@ namespace Avalonia.Markup.Xaml.Converters
{
var s = (string)value;
return FontFamily.Parse(s, context.GetBaseUri());
return FontFamily.Parse(s, context.GetContextBaseUri());
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs

@ -45,7 +45,7 @@ namespace Avalonia.Markup.Xaml.Converters
if(uri.IsAbsoluteUri && uri.IsFile)
return new WindowIcon(uri.LocalPath);
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
return new WindowIcon(assets.Open(uri, context.GetBaseUri()));
return new WindowIcon(assets.Open(uri, context.GetContextBaseUri()));
}
}
}

61
src/Markup/Avalonia.Markup.Xaml/Extensions.cs

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Portable.Xaml;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml
{
internal static class Extensions
{
public static T GetService<T>(this IServiceProvider sp) => (T)sp?.GetService(typeof(T));
public static Uri GetContextBaseUri(this IServiceProvider ctx)
{
var properService = ctx.GetService<IUriContext>();
if (properService != null)
return properService.BaseUri;
// Ugly hack with casts
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetBaseUri((ITypeDescriptorContext)ctx);
}
public static T GetFirstParent<T>(this IServiceProvider ctx) where T : class
{
var parentStack = ctx.GetService<IAvaloniaXamlIlParentStackProvider>();
if (parentStack != null)
return parentStack.Parents.OfType<T>().FirstOrDefault();
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetFirstAmbientValue<T>((ITypeDescriptorContext)ctx);
}
public static T GetLastParent<T>(this IServiceProvider ctx) where T : class
{
var parentStack = ctx.GetService<IAvaloniaXamlIlParentStackProvider>();
if (parentStack != null)
return parentStack.Parents.OfType<T>().LastOrDefault();
return Portable.Xaml.ComponentModel.TypeDescriptorExtensions.GetLastOrDefaultAmbientValue<T>(
(ITypeDescriptorContext)ctx);
}
public static IEnumerable<T> GetParents<T>(this IServiceProvider sp)
{
var stack = sp.GetService<IAvaloniaXamlIlParentStackProvider>();
if (stack != null)
return stack.Parents.OfType<T>();
var context = (ITypeDescriptorContext)sp;
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext;
var ambientProvider = context.GetService<IAmbientProvider>();
return ambientProvider.GetAllAmbientValues(schemaContext.GetXamlType(typeof(T))).OfType<T>();
}
public static Type ResolveType(this IServiceProvider ctx, string namespacePrefix, string type)
{
var tr = ctx.GetService<IXamlTypeResolver>();
string name = string.IsNullOrEmpty(namespacePrefix) ? type : $"{namespacePrefix}:{type}";
return tr?.Resolve(name);
}
}
}

11
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -29,6 +29,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return ProvideTypedValue(serviceProvider);
}
public Binding ProvideTypedValue(IServiceProvider serviceProvider)
{
var descriptorContext = (ITypeDescriptorContext)serviceProvider;
@ -49,18 +54,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
};
}
private static object GetDefaultAnchor(ITypeDescriptorContext context)
private static object GetDefaultAnchor(IServiceProvider context)
{
// If the target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
object anchor = context.GetFirstAmbientValue<IControl>();
object anchor = context.GetFirstParent<IControl>();
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
context.GetLastOrDefaultAmbientValue<IStyle>();
context.GetLastParent<IStyle>();
}
public IValueConverter Converter { get; set; }

20
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -28,14 +28,15 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public string ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider);
public IBinding ProvideTypedValue(IServiceProvider serviceProvider)
{
var context = (ITypeDescriptorContext)serviceProvider;
var provideTarget = context.GetService<IProvideValueTarget>();
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
if (!(provideTarget.TargetObject is IResourceNode))
{
_anchor = GetAnchor<IResourceNode>(context);
_anchor = serviceProvider.GetFirstParent<IResourceNode>();
}
return this;
@ -56,16 +57,5 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
return null;
}
private T GetAnchor<T>(ITypeDescriptorContext context) where T : class
{
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext;
var ambientProvider = context.GetService<IAmbientProvider>();
var xamlType = schemaContext.GetXamlType(typeof(T));
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back
// from GetAllAmbientValues aren't necessarily of the correct type.
return ambientProvider.GetAllAmbientValues(xamlType).OfType<T>().FirstOrDefault();
}
}
}

7
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

@ -54,9 +54,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
/// <inhertidoc/>
public override object ProvideValue(IServiceProvider serviceProvider)
{
return ProvideTypedValue(serviceProvider);
}
public ResourceInclude ProvideTypedValue(IServiceProvider serviceProvider)
{
var tdc = (ITypeDescriptorContext)serviceProvider;
_baseUri = tdc?.GetBaseUri();
_baseUri = tdc?.GetContextBaseUri();
return this;
}
}

34
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -28,36 +28,32 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public override object ProvideValue(IServiceProvider serviceProvider)
{
var context = (ITypeDescriptorContext)serviceProvider;
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext;
var ambientProvider = context.GetService<IAmbientProvider>();
var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceNode));
var ambientValues = ambientProvider.GetAllAmbientValues(resourceProviderType);
// Look upwards though the ambient context for IResourceProviders which might be able
// to give us the resource.
foreach (var ambientValue in ambientValues)
foreach (var resourceProvider in serviceProvider.GetParents<IResourceNode>())
{
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back
// from GetAllAmbientValues aren't necessarily of the correct type.
if (ambientValue is IResourceNode resourceProvider)
if (AvaloniaXamlLoader.UseLegacyXamlLoader
&& resourceProvider is IControl control && control.StylingParent != null)
{
if (resourceProvider is IControl control && control.StylingParent != null)
{
// If we've got to a control that has a StylingParent then it's probably
// a top level control and its StylingParent is pointing to the global
// styles. If this is case just do a FindResource on it.
return control.FindResource(ResourceKey);
}
else if (resourceProvider.TryGetResource(ResourceKey, out var value))
{
return value;
}
// If we've got to a control that has a StylingParent then it's probably
// a top level control and its StylingParent is pointing to the global
// styles. If this is case just do a FindResource on it.
return control.FindResource(ResourceKey);
}
else if (resourceProvider.TryGetResource(ResourceKey, out var value))
{
return value;
}
}
// The resource still hasn't been found, so add a delayed one-time binding.
var provideTarget = context.GetService<IProvideValueTarget>();
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
if (provideTarget.TargetObject is IControl target &&
provideTarget.TargetProperty is PropertyInfo property)

8
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleIncludeExtension.cs

@ -18,13 +18,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider);
public IStyle ProvideTypedValue(IServiceProvider serviceProvider)
{
var tdc = (ITypeDescriptorContext)serviceProvider;
return new StyleInclude(tdc.GetBaseUri()) { Source = Source };
return new StyleInclude(serviceProvider.GetContextBaseUri()) { Source = Source };
}
public Uri Source { get; set; }
}
}
}

4
src/Markup/Avalonia.Markup.Xaml/PortableXaml/TypeDescriptorExtensions.cs

@ -44,7 +44,7 @@ namespace Portable.Xaml.ComponentModel
var amb = ctx.GetService<IAmbientProvider>();
var sc = ctx.GetService<IXamlSchemaContextProvider>().SchemaContext;
// Because GetFirstAmbientValue uses XamlType.CanAssignTo it returns values that
// Because GetFirstParent uses XamlType.CanAssignTo it returns values that
// aren't actually of the correct type. Use GetAllAmbientValues instead.
return amb.GetAllAmbientValues(sc.GetXamlType(typeof(T))).OfType<T>().FirstOrDefault();
}
@ -98,4 +98,4 @@ namespace Portable.Xaml.ComponentModel
public AvaloniaXamlContext Context { get; }
}
}
}
}

5
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -25,6 +25,11 @@ namespace Avalonia.Markup.Xaml.Styling
_baseUri = baseUri;
}
public StyleInclude(IServiceProvider serviceProvider)
{
_baseUri = serviceProvider.GetContextBaseUri();
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged
{

7
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls;
using System.Collections.Generic;
@ -37,7 +38,11 @@ namespace Avalonia.Markup.Xaml.Templates
public static IControl Load(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
{
return (IControl)direct(null);
}
return ((TemplateContent)templateContent).Load();
}
}
}
}

276
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs

@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Platform;
using XamlIl.Transform;
using XamlIl.TypeSystem;
#if RUNTIME_XAML_CECIL
using TypeAttributes = Mono.Cecil.TypeAttributes;
using Mono.Cecil;
using XamlIl.Ast;
#endif
namespace Avalonia.Markup.Xaml.XamlIl
{
static class AvaloniaXamlIlRuntimeCompiler
{
#if !RUNTIME_XAML_CECIL
private static SreTypeSystem _sreTypeSystem;
private static ModuleBuilder _sreBuilder;
private static IXamlIlType _sreContextType;
private static XamlIlLanguageTypeMappings _sreMappings;
private static XamlIlXmlnsMappings _sreXmlns;
private static AssemblyBuilder _sreAsm;
private static bool _sreCanSave;
public static void DumpRuntimeCompilationResults()
{
if (_sreBuilder == null)
return;
var saveMethod = _sreAsm.GetType().GetMethods()
.FirstOrDefault(m => m.Name == "Save" && m.GetParameters().Length == 1);
if (saveMethod == null)
return;
try
{
_sreBuilder.CreateGlobalFunctions();
saveMethod.Invoke(_sreAsm, new Object[] {"XamlIlLoader.ildump"});
}
catch
{
//Ignore
}
}
static void InitializeSre()
{
if (_sreTypeSystem == null)
_sreTypeSystem = new SreTypeSystem();
if (_sreBuilder == null)
{
_sreCanSave = !(RuntimeInformation.FrameworkDescription.StartsWith(".NET Core"));
var name = new AssemblyName(Guid.NewGuid().ToString("N"));
if (_sreCanSave)
{
var define = AppDomain.CurrentDomain.GetType().GetMethods()
.FirstOrDefault(m => m.Name == "DefineDynamicAssembly"
&& m.GetParameters().Length == 3 &&
m.GetParameters()[2].ParameterType == typeof(string));
if (define != null)
_sreAsm = (AssemblyBuilder)define.Invoke(AppDomain.CurrentDomain, new object[]
{
name, (AssemblyBuilderAccess)3,
Path.GetDirectoryName(typeof(AvaloniaXamlIlRuntimeCompiler).Assembly.GetModules()[0]
.FullyQualifiedName)
});
else
_sreCanSave = false;
}
if(_sreAsm == null)
_sreAsm = AssemblyBuilder.DefineDynamicAssembly(name,
AssemblyBuilderAccess.RunAndCollect);
_sreBuilder = _sreAsm.DefineDynamicModule("XamlIlLoader.ildump");
}
if (_sreMappings == null)
_sreMappings = AvaloniaXamlIlLanguage.Configure(_sreTypeSystem);
if (_sreXmlns == null)
_sreXmlns = XamlIlXmlnsMappings.Resolve(_sreTypeSystem, _sreMappings);
if (_sreContextType == null)
_sreContextType = XamlIlContextDefinition.GenerateContextClass(
_sreTypeSystem.CreateTypeBuilder(
_sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings);
}
static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode)
{
var success = false;
try
{
var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode);
success = true;
return rv;
}
finally
{
if(!success && _sreCanSave)
DumpRuntimeCompilationResults();
}
}
static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode)
{
InitializeSre();
var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly);
var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter),
_sreContextType);
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
IXamlIlType overrideType = null;
if (rootInstance != null)
{
overrideType = _sreTypeSystem.GetType(rootInstance.GetType());
}
compiler.IsDesignMode = isDesignMode;
compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType);
var created = tb.CreateTypeInfo();
return LoadOrPopulate(created, rootInstance);
}
#endif
static object LoadOrPopulate(Type created, object rootInstance)
{
var isp = Expression.Parameter(typeof(IServiceProvider));
var epar = Expression.Parameter(typeof(object));
var populate = created.GetMethod(AvaloniaXamlIlCompiler.PopulateName);
isp = Expression.Parameter(typeof(IServiceProvider));
var populateCb = Expression.Lambda<Action<IServiceProvider, object>>(
Expression.Call(populate, isp, Expression.Convert(epar, populate.GetParameters()[1].ParameterType)),
isp, epar).Compile();
if (rootInstance == null)
{
var targetType = populate.GetParameters()[1].ParameterType;
var overrideField = targetType.GetField("!XamlIlPopulateOverride",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
if (overrideField != null)
{
overrideField.SetValue(null,
new Action<object>(
target => { populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, target); }));
try
{
return Activator.CreateInstance(targetType);
}
finally
{
overrideField.SetValue(null, null);
}
}
var createCb = Expression.Lambda<Func<IServiceProvider, object>>(
Expression.Convert(Expression.Call(
created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile();
return createCb(XamlIlRuntimeHelpers.RootServiceProviderV1);
}
else
{
populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, rootInstance);
return rootInstance;
}
}
public static object Load(Stream stream, Assembly localAssembly, object rootInstance, Uri uri,
bool isDesignMode)
{
string xaml;
using (var sr = new StreamReader(stream))
xaml = sr.ReadToEnd();
#if RUNTIME_XAML_CECIL
return LoadCecil(xaml, localAssembly, rootInstance, uri);
#else
return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode);
#endif
}
#if RUNTIME_XAML_CECIL
private static Dictionary<string, (Action<IServiceProvider, object> populate, Func<IServiceProvider, object>
build)>
s_CecilCache =
new Dictionary<string, (Action<IServiceProvider, object> populate, Func<IServiceProvider, object> build)
>();
private static string _cecilEmitDir;
private static CecilTypeSystem _cecilTypeSystem;
private static XamlIlLanguageTypeMappings _cecilMappings;
private static XamlIlXmlnsMappings _cecilXmlns;
private static bool _cecilInitialized;
static void InitializeCecil()
{
if(_cecilInitialized)
return;
var path = Assembly.GetEntryAssembly().GetModules()[0].FullyQualifiedName;
_cecilEmitDir = Path.Combine(Path.GetDirectoryName(path), "emit");
Directory.CreateDirectory(_cecilEmitDir);
var refs = new[] {path}.Concat(File.ReadAllLines(path + ".refs"));
_cecilTypeSystem = new CecilTypeSystem(refs);
_cecilMappings = AvaloniaXamlIlLanguage.Configure(_cecilTypeSystem);
_cecilXmlns = XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings);
_cecilInitialized = true;
}
private static Dictionary<string, Type> _cecilGeneratedCache = new Dictionary<string, Type>();
static object LoadCecil(string xaml, Assembly localAssembly, object rootInstance, Uri uri)
{
if (uri == null)
throw new InvalidOperationException("Please, go away");
InitializeCecil();
IXamlIlType overrideType = null;
if (rootInstance != null)
{
overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName);
}
var safeUri = uri.ToString()
.Replace(":", "_")
.Replace("/", "_")
.Replace("?", "_")
.Replace("=", "_")
.Replace(".", "_");
if (_cecilGeneratedCache.TryGetValue(safeUri, out var cached))
return LoadOrPopulate(cached, rootInstance);
var asm = _cecilTypeSystem.CreateAndRegisterAssembly(safeUri, new Version(1, 0),
ModuleKind.Dll);
var def = new TypeDefinition("XamlIlLoader", safeUri,
TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
var contextDef = new TypeDefinition("XamlIlLoader", safeUri + "_XamlIlContext",
TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(def);
asm.MainModule.Types.Add(contextDef);
var tb = _cecilTypeSystem.CreateTypeBuilder(def);
var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_cecilTypeSystem,
localAssembly == null ? null : _cecilTypeSystem.FindAssembly(localAssembly.GetName().Name),
_cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings),
AvaloniaXamlIlLanguage.CustomValueConverter),
_cecilTypeSystem.CreateTypeBuilder(contextDef));
compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType);
var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll");
using(var f = File.Create(asmPath))
asm.Write(f);
var loaded = Assembly.LoadFile(asmPath)
.GetTypes().First(x => x.Name == safeUri);
_cecilGeneratedCache[safeUri] = loaded;
return LoadOrPopulate(loaded, rootInstance);
}
#endif
}
}

124
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Parsers;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class AvaloniaXamlIlCompiler : XamlIlCompiler
{
private readonly XamlIlTransformerConfiguration _configuration;
private readonly IXamlIlType _contextType;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
private AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration) : base(configuration, true)
{
_configuration = configuration;
void InsertAfter<T>(params IXamlIlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);
void InsertBefore<T>(params IXamlIlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T), t);
// Before everything else
Transformers.Insert(0, new XNameTransformer());
Transformers.Insert(1, new IgnoredDirectivesTransformer());
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
Transformers.Insert(3, new AvaloniaBindingExtensionHackTransformer());
// Targeted
InsertBefore<XamlIlPropertyReferenceResolver>(new AvaloniaXamlIlTransformInstanceAttachedProperties());
InsertAfter<XamlIlPropertyReferenceResolver>(new AvaloniaXamlIlAvaloniaPropertyResolver());
InsertBefore<XamlIlContentConvertTransformer>(
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer()
);
// After everything else
Transformers.Add(new AddNameScopeRegistration());
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
}
public AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration,
IXamlIlTypeBuilder contextTypeBuilder) : this(configuration)
{
_contextType = CreateContextType(contextTypeBuilder);
}
public AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration,
IXamlIlType contextType) : this(configuration)
{
_contextType = contextType;
}
public const string PopulateName = "__AvaloniaXamlIlPopulate";
public const string BuildName = "__AvaloniaXamlIlBuild";
public bool IsDesignMode
{
get => _designTransformer.IsDesignMode;
set => _designTransformer.IsDesignMode = value;
}
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlIlTypeBuilder tb, IXamlIlType overrideRootType)
{
var parsed = XDocumentXamlIlParser.Parse(xaml, new Dictionary<string, string>
{
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
});
var rootObject = (XamlIlAstObjectNode)parsed.Root;
var classDirective = rootObject.Children
.OfType<XamlIlAstXmlDirective>().FirstOrDefault(x =>
x.Namespace == XamlNamespaces.Xaml2006
&& x.Name == "Class");
var rootType =
classDirective != null ?
new XamlIlAstClrTypeReference(classDirective,
_configuration.TypeSystem.GetType(((XamlIlAstTextNode)classDirective.Values[0]).Text),
false) :
XamlIlTypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlIlAstXmlTypeReference)rootObject.Type, true);
if (overrideRootType != null)
{
if (!rootType.Type.IsAssignableFrom(overrideRootType))
throw new XamlIlLoadException(
$"Unable to substitute {rootType.Type.GetFqn()} with {overrideRootType.GetFqn()}", rootObject);
rootType = new XamlIlAstClrTypeReference(rootObject, overrideRootType, false);
}
rootObject.Type = rootType;
Transform(parsed);
Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource);
}
}
}

176
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -0,0 +1,176 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
/*
This file is used in the build task.
ONLY use types from netstandard and XamlIl. NO dependencies on Avalonia are allowed. Only strings.
No, nameof isn't welcome here either
*/
class AvaloniaXamlIlLanguage
{
public static XamlIlLanguageTypeMappings Configure(IXamlIlTypeSystem typeSystem)
{
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var assignBindingAttribute = typeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
var bindingType = typeSystem.GetType("Avalonia.Data.IBinding");
var rv = new XamlIlLanguageTypeMappings(typeSystem)
{
SupportInitialize = typeSystem.GetType("System.ComponentModel.ISupportInitialize"),
XmlnsAttributes =
{
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
typeSystem.FindType("Portable.Xaml.Markup.XmlnsDefinitionAttribute")
},
ContentAttributes =
{
typeSystem.GetType("Avalonia.Metadata.ContentAttribute")
},
ProvideValueTarget = typeSystem.GetType("Portable.Xaml.Markup.IProvideValueTarget"),
RootObjectProvider = typeSystem.GetType("Portable.Xaml.IRootObjectProvider"),
UriContextProvider = typeSystem.GetType("Portable.Xaml.Markup.IUriContext"),
ParentStackProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"),
XmlNamespaceInfoProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlXmlNamespaceInfoProvider"),
DeferredContentPropertyAttributes = {typeSystem.GetType("Avalonia.Metadata.TemplateContentAttribute")},
DeferredContentExecutorCustomization =
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"),
UsableDuringInitializationAttributes =
{
typeSystem.GetType("Portable.Xaml.Markup.UsableDuringInitializationAttribute"),
typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"),
},
InnerServiceProviderFactoryMethod =
runtimeHelpers.FindMethod(m => m.Name == "CreateInnerServiceProviderV1"),
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit,
};
rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv);
return rv;
}
class AttributeResolver : IXamlIlCustomAttributeResolver
{
private readonly IXamlIlType _typeConverterAttribute;
private readonly List<KeyValuePair<IXamlIlType, IXamlIlType>> _converters =
new List<KeyValuePair<IXamlIlType, IXamlIlType>>();
private readonly IXamlIlType _avaloniaList;
private readonly IXamlIlType _avaloniaListConverter;
public AttributeResolver(IXamlIlTypeSystem typeSystem, XamlIlLanguageTypeMappings mappings)
{
_typeConverterAttribute = mappings.TypeConverterAttributes.First();
void AddType(IXamlIlType type, IXamlIlType conv)
=> _converters.Add(new KeyValuePair<IXamlIlType, IXamlIlType>(type, conv));
void Add(string type, string conv)
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv));
//Add("Avalonia.AvaloniaProperty","Avalonia.Markup.Xaml.Converters.AvaloniaPropertyTypeConverter");
Add("Avalonia.Media.Imaging.IBitmap","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
var ilist = typeSystem.GetType("System.Collections.Generic.IList`1");
AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")),
typeSystem.GetType("Avalonia.Markup.Xaml.Converters.PointsListTypeConverter"));
Add("Avalonia.Controls.Templates.IMemberSelector",
"Avalonia.Markup.Xaml.Converters.MemberSelectorTypeConverter");
Add("Avalonia.Styling.Selector","Avalonia.Markup.Xaml.Converters.SelectorTypeConverter");
Add("Avalonia.Controls.WindowIcon","Avalonia.Markup.Xaml.Converters.IconTypeConverter");
Add("System.Globalization.CultureInfo", "System.ComponentModel.CultureInfoConverter");
Add("System.Uri", "Avalonia.Markup.Xaml.Converters.AvaloniaUriTypeConverter");
Add("System.TimeSpan", "Avalonia.Markup.Xaml.Converters.TimeSpanTypeConverter");
Add("Avalonia.Media.FontFamily","Avalonia.Markup.Xaml.Converters.FontFamilyTypeConverter");
_avaloniaList = typeSystem.GetType("Avalonia.Collections.AvaloniaList`1");
_avaloniaListConverter = typeSystem.GetType("Avalonia.Collections.AvaloniaListConverter`1");
}
IXamlIlType LookupConverter(IXamlIlType type)
{
foreach(var p in _converters)
if (p.Key.Equals(type))
return p.Value;
if (type.GenericTypeDefinition?.Equals(_avaloniaList) == true)
return _avaloniaListConverter.MakeGenericType(type.GenericArguments[0]);
return null;
}
class ConstructedAttribute : IXamlIlCustomAttribute
{
public bool Equals(IXamlIlCustomAttribute other) => false;
public IXamlIlType Type { get; }
public List<object> Parameters { get; }
public Dictionary<string, object> Properties { get; }
public ConstructedAttribute(IXamlIlType type, List<object> parameters, Dictionary<string, object> properties)
{
Type = type;
Parameters = parameters ?? new List<object>();
Properties = properties ?? new Dictionary<string, object>();
}
}
public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlType type, IXamlIlType attributeType)
{
if (attributeType.Equals(_typeConverterAttribute))
{
var conv = LookupConverter(type);
if (conv != null)
return new ConstructedAttribute(_typeConverterAttribute, new List<object>() {conv}, null);
}
return null;
}
public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlProperty property, IXamlIlType attributeType)
{
return null;
}
}
public static bool CustomValueConverter(XamlIlAstTransformationContext context,
IXamlIlAstValueNode node, IXamlIlType type, out IXamlIlAstValueNode result)
{
if (type.FullName == "System.TimeSpan"
&& node is XamlIlAstTextNode tn
&& !tn.Text.Contains(":"))
{
var seconds = double.Parse(tn.Text, CultureInfo.InvariantCulture);
result = new XamlIlStaticOrTargetedReturnMethodCallNode(tn,
type.FindMethod("FromSeconds", type, false, context.Configuration.WellKnownTypes.Double),
new[]
{
new XamlIlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds)
});
return true;
}
if (type.FullName == "Avalonia.AvaloniaProperty")
{
var scope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
if (scope == null)
throw new XamlIlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node);
if (!(node is XamlIlAstTextNode text))
throw new XamlIlLoadException("Property should be a text node", node);
result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text);
return true;
}
result = null;
return false;
}
}
}

94
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs

@ -0,0 +1,94 @@
using System;
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AddNameScopeRegistration : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlPropertyAssignmentNode pa
&& pa.Property.Name == "Name"
&& pa.Property.DeclaringType.FullName == "Avalonia.StyledElement")
{
if (context.ParentNodes().FirstOrDefault() is XamlIlManipulationGroupNode mg
&& mg.Children.OfType<ScopeRegistrationNode>().Any())
return node;
IXamlIlAstValueNode value = null;
for (var c = 0; c < pa.Values.Count; c++)
if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
{
value = pa.Values[c];
if (!(value is XamlIlAstTextNode))
{
var local = new XamlIlAstCompilerLocalNode(value);
// Wrap original in local initialization
pa.Values[c] = new XamlIlAstLocalInitializationNodeEmitter(value, value, local);
// Use local
value = local;
}
break;
}
if (value != null)
return new XamlIlManipulationGroupNode(pa)
{
Children =
{
pa,
new ScopeRegistrationNode(value)
}
};
}
return node;
}
class ScopeRegistrationNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
{
public IXamlIlAstValueNode Value { get; set; }
public ScopeRegistrationNode(IXamlIlAstValueNode value) : base(value)
{
Value = value;
}
public override void VisitChildren(IXamlIlAstVisitor visitor)
=> Value = (IXamlIlAstValueNode)Value.Visit(visitor);
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var exts = context.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScopeExtensions");
var findNameScope = exts.FindMethod(m => m.Name == "FindNameScope");
var registerMethod = findNameScope.ReturnType.FindMethod(m => m.Name == "Register");
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object))
using (var nameScopeLoc = context.GetLocal(findNameScope.ReturnType))
{
var exit = codeGen.DefineLabel();
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// var scope = target.FindNameScope()
.Ldloc(targetLoc.Local)
.Castclass(findNameScope.Parameters[0])
.EmitCall(findNameScope)
.Stloc(nameScopeLoc.Local)
// if({scope} != null) goto call;
.Ldloc(nameScopeLoc.Local)
.Brfalse(exit)
.Ldloc(nameScopeLoc.Local);
context.Emit(Value, codeGen, Value.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(registerMethod)
.MarkLabel(exit);
}
return XamlIlNodeEmitResult.Void(1);
}
}
}
}

20
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs

@ -0,0 +1,20 @@
using XamlIl.Ast;
using XamlIl.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaBindingExtensionHackTransformer : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
// Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `<Foo>` syntax
// This is the legacy of Portable.Xaml, so we emulate that behavior here
if (node is XamlIlAstXmlTypeReference tref
&& tref.Name == "Binding"
&& tref.XmlNamespace == "https://github.com/avaloniaui")
tref.IsMarkupExtension = true;
return node;
}
}
}

24
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs

@ -0,0 +1,24 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlAstClrProperty prop)
{
var n = prop.Name + "Property";
var field =
prop.DeclaringType.Fields
.FirstOrDefault(f => f.Name == n);
if (field != null)
return new XamlIlAvaloniaProperty(prop, field, context.GetAvaloniaTypes());
}
return node;
}
}
}

47
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs

@ -0,0 +1,47 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlConstructorServiceProviderTransformer : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlAstObjectNode on && on.Arguments.Count == 0)
{
var ctors = on.Type.GetClrType().Constructors;
if (!ctors.Any(c => c.IsPublic && !c.IsStatic && c.Parameters.Count == 0))
{
var sp = context.Configuration.TypeMappings.ServiceProvider;
if (ctors.Any(c =>
c.IsPublic && !c.IsStatic && c.Parameters.Count == 1 && c.Parameters[0]
.Equals(sp)))
{
on.Arguments.Add(new InjectServiceProviderNode(sp, on));
}
}
}
return node;
}
class InjectServiceProviderNode : XamlIlAstNode, IXamlIlAstValueNode,IXamlIlAstNodeNeedsParentStack,
IXamlIlAstEmitableNode
{
public InjectServiceProviderNode(IXamlIlType type, IXamlIlLineInfo lineInfo) : base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(lineInfo, type, false);
}
public IXamlIlAstTypeReference Type { get; }
public bool NeedsParentStack => true;
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
codeGen.Ldloc(context.ContextLocal);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
}
}
}
}

71
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs

@ -0,0 +1,71 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (!(node is XamlIlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Markup.Xaml.Templates.ControlTemplate"))
return node;
var tt = on.Children.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(ch =>
ch.Property.GetClrProperty().Name == "TargetType");
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode)
// Deja vu. I've just been in this place before
return node;
IXamlIlAstTypeReference targetType;
var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control");
if ((tt?.Values.FirstOrDefault() is XamlIlTypeExtensionNode tn))
{
targetType = tn.Type;
}
else
{
var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault();
if (parentScope?.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
targetType = parentScope.TargetType;
else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlIlAstObjectNode directParentNode
&& templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType()))
targetType = directParentNode.Type;
else
targetType = new XamlIlAstClrTypeReference(node,
templatableBaseType, false);
}
return new AvaloniaXamlIlTargetTypeMetadataNode(on, targetType,
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
}
}
class AvaloniaXamlIlTargetTypeMetadataNode : XamlIlValueWithSideEffectNodeBase
{
public IXamlIlAstTypeReference TargetType { get; set; }
public ScopeTypes ScopeType { get; }
public enum ScopeTypes
{
Style,
ControlTemplate,
Transitions
}
public AvaloniaXamlIlTargetTypeMetadataNode(IXamlIlAstValueNode value, IXamlIlAstTypeReference targetType,
ScopeTypes type)
: base(value, value)
{
TargetType = targetType;
ScopeType = type;
}
}
}

63
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs

@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlDesignPropertiesTransformer : IXamlIlAstTransformer
{
public bool IsDesignMode { get; set; }
private static Dictionary<string, string> DesignDirectives = new Dictionary<string, string>()
{
["DataContext"] = "DataContext",
["DesignWidth"] = "Width", ["DesignHeight"] = "Height", ["PreviewWith"] = "PreviewWith"
};
private const string AvaloniaNs = "https://github.com/avaloniaui";
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlAstObjectNode on)
{
for (var c=0; c<on.Children.Count;)
{
var ch = on.Children[c];
if (ch is XamlIlAstXmlDirective directive
&& directive.Namespace == XamlNamespaces.Blend2008
&& DesignDirectives.TryGetValue(directive.Name, out var mapTo))
{
if (!IsDesignMode)
// Just remove it from AST in non-design mode
on.Children.RemoveAt(c);
else
{
// Map to an actual property in `Design` class
on.Children[c] = new XamlIlAstXamlPropertyValueNode(ch,
new XamlIlAstNamePropertyReference(ch,
new XamlIlAstXmlTypeReference(ch, AvaloniaNs, "Design"),
mapTo, on.Type), directive.Values);
c++;
}
}
// Remove all "Design" attached properties in non-design mode
else if (
!IsDesignMode
&& ch is XamlIlAstXamlPropertyValueNode pv
&& pv.Property is XamlIlAstNamePropertyReference pref
&& pref.DeclaringType is XamlIlAstXmlTypeReference dref
&& dref.XmlNamespace == AvaloniaNs && dref.Name == "Design"
)
{
on.Children.RemoveAt(c);
}
else
c++;
}
}
return node;
}
}
}

17
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs

@ -0,0 +1,17 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlMetadataRemover : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is AvaloniaXamlIlTargetTypeMetadataNode md)
return md.Value;
return node;
}
}
}

338
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -0,0 +1,338 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Parsers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlSelectorTransformer : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (!(node is XamlIlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
return node;
var pn = on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (pn == null)
return node;
if (pn.Values.Count != 1)
throw new XamlIlParseException("Selector property should should have exactly one value", node);
if (pn.Values[0] is XamlIlSelectorNode)
//Deja vu. I've just been in this place before
return node;
if (!(pn.Values[0] is XamlIlAstTextNode tn))
throw new XamlIlParseException("Selector property should be a text node", node);
var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
XamlIlSelectorNode Create(IEnumerable<SelectorGrammar.ISyntax> syntax,
Func<string, string, XamlIlAstClrTypeReference> typeResolver)
{
XamlIlSelectorNode result = initialNode;
XamlIlOrSelectorNode results = null;
foreach (var i in syntax)
{
switch (i)
{
case SelectorGrammar.OfTypeSyntax ofType:
result = new XamlIlTypeSelector(result, typeResolver(ofType.Xmlns, ofType.TypeName).Type, true);
break;
case SelectorGrammar.IsSyntax @is:
result = new XamlIlTypeSelector(result, typeResolver(@is.Xmlns, @is.TypeName).Type, false);
break;
case SelectorGrammar.ClassSyntax @class:
result = new XamlIlStringSelector(result, XamlIlStringSelector.SelectorType.Class, @class.Class);
break;
case SelectorGrammar.NameSyntax name:
result = new XamlIlStringSelector(result, XamlIlStringSelector.SelectorType.Name, name.Name);
break;
case SelectorGrammar.PropertySyntax property:
{
var type = result?.TargetType;
if (type == null)
throw new XamlIlParseException("Property selectors must be applied to a type.", node);
var targetProperty =
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
if (targetProperty == null)
throw new XamlIlParseException($"Cannot find '{property.Property}' on '{type}", node);
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlIlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String),
targetProperty.PropertyType, out var typedValue))
throw new XamlIlParseException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
result = new XamlIlPropertyEqualsSelector(result, targetProperty, typedValue);
break;
}
case SelectorGrammar.ChildSyntax child:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Child);
break;
case SelectorGrammar.DescendantSyntax descendant:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Descendant);
break;
case SelectorGrammar.TemplateSyntax template:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Template);
break;
case SelectorGrammar.NotSyntax not:
result = new XamlIlNotSelector(result, Create(not.Argument, typeResolver));
break;
case SelectorGrammar.CommaSyntax comma:
if (results == null)
results = new XamlIlOrSelectorNode(node, selectorType);
results.Add(result);
result = initialNode;
break;
default:
throw new XamlIlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
}
return results ?? result;
}
IEnumerable<SelectorGrammar.ISyntax> parsed;
try
{
parsed = SelectorGrammar.Parse(tn.Text);
}
catch (Exception e)
{
throw new XamlIlParseException("Unable to parse selector: " + e.Message, node);
}
var selector = Create(parsed, (p, n)
=> XamlIlTypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
pn.Values[0] = selector;
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlIlAstClrTypeReference(selector, selector.TargetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
}
abstract class XamlIlSelectorNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode
{
protected XamlIlSelectorNode Previous { get; }
public abstract IXamlIlType TargetType { get; }
public XamlIlSelectorNode(XamlIlSelectorNode previous,
IXamlIlLineInfo info = null,
IXamlIlType selectorType = null) : base(info ?? previous)
{
Previous = previous;
Type = selectorType == null ? previous.Type : new XamlIlAstClrTypeReference(this, selectorType, false);
}
public IXamlIlAstTypeReference Type { get; }
public virtual XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
if (Previous != null)
context.Emit(Previous, codeGen, Type.GetClrType());
DoEmit(context, codeGen);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
}
protected abstract void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen);
protected void EmitCall(XamlIlEmitContext context, IXamlIlEmitter codeGen, Func<IXamlIlMethod, bool> method)
{
var selectors = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Selectors");
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 &&
m.Parameters[0].FullName == "Avalonia.Styling.Selector"
&& method(m));
codeGen.EmitCall(found);
}
}
class XamlIlSelectorInitialNode : XamlIlSelectorNode
{
public XamlIlSelectorInitialNode(IXamlIlLineInfo info,
IXamlIlType selectorType) : base(null, info, selectorType)
{
}
public override IXamlIlType TargetType => null;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen) => codeGen.Ldnull();
}
class XamlIlTypeSelector : XamlIlSelectorNode
{
public bool Concrete { get; }
public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlIlType type, bool concrete) : base(previous)
{
TargetType = type;
Concrete = concrete;
}
public override IXamlIlType TargetType { get; }
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var name = Concrete ? "OfType" : "Is";
codeGen.Ldtype(TargetType);
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 2 && m.Parameters[1].FullName == "System.Type");
}
}
class XamlIlStringSelector : XamlIlSelectorNode
{
public string String { get; set; }
public enum SelectorType
{
Class,
Name
}
private SelectorType _type;
public XamlIlStringSelector(XamlIlSelectorNode previous, SelectorType type, string s) : base(previous)
{
_type = type;
String = s;
}
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
codeGen.Ldstr(String);
var name = _type.ToString();
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 2 && m.Parameters[1].FullName == "System.String");
}
}
class XamlIlCombinatorSelector : XamlIlSelectorNode
{
private readonly SelectorType _type;
public enum SelectorType
{
Child,
Descendant,
Template
}
public XamlIlCombinatorSelector(XamlIlSelectorNode previous, SelectorType type) : base(previous)
{
_type = type;
}
public override IXamlIlType TargetType => null;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var name = _type.ToString();
EmitCall(context, codeGen,
m => m.Name == name && m.Parameters.Count == 1);
}
}
class XamlIlNotSelector : XamlIlSelectorNode
{
public XamlIlSelectorNode Argument { get; }
public XamlIlNotSelector(XamlIlSelectorNode previous, XamlIlSelectorNode argument) : base(previous)
{
Argument = argument;
}
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
context.Emit(Argument, codeGen, Type.GetClrType());
EmitCall(context, codeGen,
m => m.Name == "Not" && m.Parameters.Count == 2 && m.Parameters[1].Equals(Type.GetClrType()));
}
}
class XamlIlPropertyEqualsSelector : XamlIlSelectorNode
{
public XamlIlPropertyEqualsSelector(XamlIlSelectorNode previous,
IXamlIlProperty property,
IXamlIlAstValueNode value)
: base(previous)
{
Property = property;
Value = value;
}
public IXamlIlProperty Property { get; set; }
public IXamlIlAstValueNode Value { get; set; }
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
throw new XamlIlLoadException(
$"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
this);
context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
EmitCall(context, codeGen,
m => m.Name == "PropertyEquals"
&& m.Parameters.Count == 3
&& m.Parameters[1].FullName == "Avalonia.AvaloniaProperty"
&& m.Parameters[2].Equals(context.Configuration.WellKnownTypes.Object));
}
}
class XamlIlOrSelectorNode : XamlIlSelectorNode
{
List<XamlIlSelectorNode> _selectors = new List<XamlIlSelectorNode>();
public XamlIlOrSelectorNode(IXamlIlLineInfo info, IXamlIlType selectorType) : base(null, info, selectorType)
{
}
public void Add(XamlIlSelectorNode node)
{
_selectors.Add(node);
}
//TODO: actually find the type
public override IXamlIlType TargetType => _selectors.FirstOrDefault()?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
if (_selectors.Count == 0)
throw new XamlIlLoadException("Invalid selector count", this);
if (_selectors.Count == 1)
{
_selectors[0].Emit(context, codeGen);
return;
}
var listType = context.Configuration.TypeSystem.FindType("System.Collections.Generic.List`1")
.MakeGenericType(base.Type.GetClrType());
var add = listType.FindMethod("Add", context.Configuration.WellKnownTypes.Void, false, Type.GetClrType());
codeGen
.Newobj(listType.FindConstructor());
foreach (var s in _selectors)
{
codeGen.Dup();
context.Emit(s, codeGen, Type.GetClrType());
codeGen.EmitCall(add, true);
}
EmitCall(context, codeGen,
m => m.Name == "Or" && m.Parameters.Count == 1 && m.Parameters[0].Name.StartsWith("IReadOnlyList"));
}
}
}

108
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlSetterTransformer : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (!(node is XamlIlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node;
var parent = context.ParentNodes().OfType<XamlIlAstObjectNode>()
.FirstOrDefault(x => x.Type.GetClrType().FullName == "Avalonia.Styling.Style");
if (parent == null)
throw new XamlIlParseException(
"Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node);
var selectorProperty = parent.Children.OfType<XamlIlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (selectorProperty == null)
throw new XamlIlParseException(
"Can not find parent Style Selector", node);
var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode;
if (selector?.TargetType == null)
throw new XamlIlParseException(
"Can not resolve parent Style Selector type", node);
var property = @on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
if (property == null)
throw new XamlIlParseException("Setter without a property is not valid", node);
var propertyName = property.Values.OfType<XamlIlAstTextNode>().FirstOrDefault()?.Text;
if (propertyName == null)
throw new XamlIlParseException("Setter.Property must be a string", node);
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
property.Values = new List<IXamlIlAstValueNode>
{
avaloniaPropertyNode
};
var valueProperty = on.Children
.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode)
{
var propType = avaloniaPropertyNode.Property.Getter?.ReturnType
?? avaloniaPropertyNode.Property.Setters.First().Parameters[0];
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
propType, out var converted))
throw new XamlIlParseException(
$"Unable to convert property value to {propType.GetFqn()}",
valueProperty.Values[0]);
valueProperty.Property = new SetterValueProperty(valueProperty.Property,
on.Type.GetClrType(), propType, context.GetAvaloniaTypes());
}
return node;
}
class SetterValueProperty : XamlIlAstClrProperty
{
public SetterValueProperty(IXamlIlLineInfo line, IXamlIlType setterType, IXamlIlType targetType,
AvaloniaXamlIlWellKnownTypes types)
: base(line, "Value", setterType, null)
{
Getter = setterType.Methods.First(m => m.Name == "get_Value");
var method = setterType.Methods.First(m => m.Name == "set_Value");
Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding));
Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType));
Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType));
}
class XamlIlDirectCallPropertySetter : IXamlIlPropertySetter
{
private readonly IXamlIlMethod _method;
private readonly IXamlIlType _type;
public IXamlIlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void Emit(IXamlIlEmitter codegen)
{
if (_type.IsValueType)
codegen.Box(_type);
codegen.EmitCall(_method, true);
}
public XamlIlDirectCallPropertySetter(IXamlIlMethod method, IXamlIlType type)
{
_method = method;
_type = type;
Parameters = new[] {type};
TargetType = method.ThisOrFirstParameter();
}
}
}
}
}

193
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs

@ -0,0 +1,193 @@
using System.Collections.Generic;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlTransformInstanceAttachedProperties : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlAstNamePropertyReference prop
&& prop.TargetType is XamlIlAstClrTypeReference targetRef
&& prop.DeclaringType is XamlIlAstClrTypeReference declaringRef)
{
// Target and declared type aren't assignable but both inherit from AvaloniaObject
var avaloniaObject = context.Configuration.TypeSystem.FindType("Avalonia.AvaloniaObject");
if (avaloniaObject.IsAssignableFrom(targetRef.Type)
&& avaloniaObject.IsAssignableFrom(declaringRef.Type)
&& !targetRef.Type.IsAssignableFrom(declaringRef.Type))
{
// Instance property
var clrProp = declaringRef.Type.GetAllProperties().FirstOrDefault(p => p.Name == prop.Name);
if (clrProp != null
&& (clrProp.Getter?.IsStatic == false || clrProp.Setter?.IsStatic == false))
{
var declaringType = (clrProp.Getter ?? clrProp.Setter)?.DeclaringType;
var avaloniaPropertyFieldName = prop.Name + "Property";
var avaloniaPropertyField = declaringType.Fields.FirstOrDefault(f => f.IsStatic && f.Name == avaloniaPropertyFieldName);
if (avaloniaPropertyField != null)
{
var avaloniaPropertyType = avaloniaPropertyField.FieldType;
while (avaloniaPropertyType != null
&& !(avaloniaPropertyType.Namespace == "Avalonia"
&& (avaloniaPropertyType.Name == "AvaloniaProperty"
|| avaloniaPropertyType.Name == "AvaloniaProperty`1"
)))
{
// Attached properties are handled by vanilla XamlIl
if (avaloniaPropertyType.Name.StartsWith("AttachedProperty"))
return node;
avaloniaPropertyType = avaloniaPropertyType.BaseType;
}
if (avaloniaPropertyType == null)
return node;
if (avaloniaPropertyType.GenericArguments?.Count > 1)
return node;
var propertyType = avaloniaPropertyType.GenericArguments?.Count == 1 ?
avaloniaPropertyType.GenericArguments[0] :
context.Configuration.WellKnownTypes.Object;
return new AvaloniaAttachedInstanceProperty(prop, context.Configuration,
declaringType, propertyType, avaloniaPropertyType, avaloniaObject,
avaloniaPropertyField);
}
}
}
}
return node;
}
class AvaloniaAttachedInstanceProperty : XamlIlAstClrProperty, IXamlIlAvaloniaProperty
{
private readonly XamlIlTransformerConfiguration _config;
private readonly IXamlIlType _declaringType;
private readonly IXamlIlType _avaloniaPropertyType;
private readonly IXamlIlType _avaloniaObject;
private readonly IXamlIlField _field;
public AvaloniaAttachedInstanceProperty(XamlIlAstNamePropertyReference prop,
XamlIlTransformerConfiguration config,
IXamlIlType declaringType,
IXamlIlType type,
IXamlIlType avaloniaPropertyType,
IXamlIlType avaloniaObject,
IXamlIlField field) : base(prop, prop.Name,
declaringType, null)
{
_config = config;
_declaringType = declaringType;
_avaloniaPropertyType = avaloniaPropertyType;
// XamlIl doesn't support generic methods yet
if (_avaloniaPropertyType.GenericArguments?.Count > 0)
_avaloniaPropertyType = _avaloniaPropertyType.BaseType;
_avaloniaObject = avaloniaObject;
_field = field;
PropertyType = type;
Setters.Add(new SetterMethod(this));
Getter = new GetterMethod(this);
}
public IXamlIlType PropertyType { get; }
public IXamlIlField AvaloniaProperty => _field;
class SetterMethod : IXamlIlPropertySetter
{
private readonly AvaloniaAttachedInstanceProperty _parent;
public SetterMethod(AvaloniaAttachedInstanceProperty parent)
{
_parent = parent;
Parameters = new[] {_parent._avaloniaObject, _parent.PropertyType};
}
public IXamlIlType TargetType => _parent.DeclaringType;
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void Emit(IXamlIlEmitter emitter)
{
var so = _parent._config.WellKnownTypes.Object;
var method = _parent._avaloniaObject
.FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "SetValue"
&&
m.Parameters.Count == 3
&& m.Parameters[0].Equals(_parent._avaloniaPropertyType)
&& m.Parameters[1].Equals(so)
&& m.Parameters[2].IsEnum
);
if (method == null)
throw new XamlIlTypeSystemException(
"Unable to find SetValue(AvaloniaProperty, object, BindingPriority) on AvaloniaObject");
using (var loc = emitter.LocalsPool.GetLocal(_parent.PropertyType))
emitter
.Stloc(loc.Local)
.Ldsfld(_parent._field)
.Ldloc(loc.Local);
if(_parent.PropertyType.IsValueType)
emitter.Box(_parent.PropertyType);
emitter
.Ldc_I4(0)
.EmitCall(method);
}
}
class GetterMethod : IXamlIlCustomEmitMethod
{
public GetterMethod(AvaloniaAttachedInstanceProperty parent)
{
Parent = parent;
DeclaringType = parent._declaringType;
Name = "AvaloniaObject:GetValue_" + Parent.Name;
Parameters = new[] {parent._avaloniaObject};
}
public AvaloniaAttachedInstanceProperty Parent { get; }
public bool IsPublic => true;
public bool IsStatic => true;
public string Name { get; protected set; }
public IXamlIlType DeclaringType { get; }
public bool Equals(IXamlIlMethod other) =>
other is GetterMethod m && m.Name == Name && m.DeclaringType.Equals(DeclaringType);
public IXamlIlType ReturnType => Parent.PropertyType;
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void EmitCall(IXamlIlEmitter emitter)
{
var method = Parent._avaloniaObject
.FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "GetValue"
&&
m.Parameters.Count == 1
&& m.Parameters[0].Equals(Parent._avaloniaPropertyType));
if (method == null)
throw new XamlIlTypeSystemException(
"Unable to find T GetValue<T>(AvaloniaProperty<T>) on AvaloniaObject");
emitter
.Ldsfld(Parent._field)
.EmitCall(method);
if (Parent.PropertyType.IsValueType)
emitter.Unbox_Any(Parent.PropertyType);
}
}
}
}
}

28
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs

@ -0,0 +1,28 @@
using XamlIl.Ast;
using XamlIl.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlTransitionsTypeMetadataTransformer : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlAstObjectNode on)
{
foreach (var ch in on.Children)
{
if (ch is XamlIlAstXamlPropertyValueNode pn
&& pn.Property.GetClrProperty().Getter?.ReturnType.Equals(context.GetAvaloniaTypes().Transitions) == true)
{
for (var c = 0; c < pn.Values.Count; c++)
{
pn.Values[c] = new AvaloniaXamlIlTargetTypeMetadataNode(pn.Values[c], on.Type,
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Transitions);
}
}
}
}
return node;
}
}
}

53
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -0,0 +1,53 @@
using XamlIl.Transform;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlWellKnownTypes
{
public IXamlIlType AvaloniaObject { get; }
public IXamlIlType IAvaloniaObject { get; }
public IXamlIlType BindingPriority { get; }
public IXamlIlType AvaloniaObjectExtensions { get; }
public IXamlIlType AvaloniaProperty { get; }
public IXamlIlType IBinding { get; }
public IXamlIlMethod AvaloniaObjectBindMethod { get; }
public IXamlIlMethod AvaloniaObjectSetValueMethod { get; }
public IXamlIlType IDisposable { get; }
public XamlIlTypeWellKnownTypes XamlIlTypes { get; }
public IXamlIlType Transitions { get; }
public IXamlIlType AssignBindingAttribute { get; }
public IXamlIlType UnsetValueType { get; }
public AvaloniaXamlIlWellKnownTypes(XamlIlAstTransformationContext ctx)
{
XamlIlTypes = ctx.Configuration.WellKnownTypes;
AvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObject");
IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject");
AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
AvaloniaProperty = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty");
BindingPriority = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.BindingPriority");
IBinding = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.IBinding");
IDisposable = ctx.Configuration.TypeSystem.GetType("System.IDisposable");
Transitions = ctx.Configuration.TypeSystem.GetType("Avalonia.Animation.Transitions");
AssignBindingAttribute = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject,
AvaloniaProperty,
IBinding, ctx.Configuration.WellKnownTypes.Object);
UnsetValueType = ctx.Configuration.TypeSystem.GetType("Avalonia.UnsetValueType");
AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void,
false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
}
}
static class AvaloniaXamlIlWellKnownTypesExtensions
{
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlIlAstTransformationContext ctx)
{
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))
return rv;
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx));
return rv;
}
}
}

27
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs

@ -0,0 +1,27 @@
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class IgnoredDirectivesTransformer : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlAstObjectNode no)
{
foreach (var d in no.Children.OfType<XamlIlAstXmlDirective>().ToList())
{
if (d.Namespace == XamlNamespaces.Xaml2006)
{
if (d.Name == "Precompile" || d.Name == "Class")
no.Children.Remove(d);
}
}
}
return node;
}
}
}

36
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs

@ -0,0 +1,36 @@
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class XNameTransformer : IXamlIlAstTransformer
{
/// <summary>
/// Converts x:Name directives to regular Name assignments
/// </summary>
/// <returns></returns>
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlAstObjectNode on)
{
for (var c =0; c< on.Children.Count;c++)
{
var ch = on.Children[c];
if (ch is XamlIlAstXmlDirective d
&& d.Namespace == XamlNamespaces.Xaml2006
&& d.Name == "Name")
on.Children[c] = new XamlIlAstXamlPropertyValueNode(d,
new XamlIlAstNamePropertyReference(d, on.Type, "Name", on.Type),
d.Values);
}
}
return node;
}
}
}

186
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using Avalonia.Markup.Xaml.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Utilities;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class XamlIlAvaloniaPropertyHelper
{
public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, XamlIlAstClrProperty property)
{
if (property is IXamlIlAvaloniaProperty ap)
{
emitter.Ldsfld(ap.AvaloniaProperty);
return true;
}
var type = property.DeclaringType;
var name = property.Name + "Property";
var found = type.Fields.FirstOrDefault(f => f.IsStatic && f.Name == name);
if (found == null)
return false;
emitter.Ldsfld(found);
return true;
}
public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, IXamlIlProperty property)
{
var type = (property.Getter ?? property.Setter).DeclaringType;
var name = property.Name + "Property";
var found = type.Fields.FirstOrDefault(f => f.IsStatic && f.Name == name);
if (found == null)
return false;
emitter.Ldsfld(found);
return true;
}
public static XamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context,
string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo)
{
XamlIlAstNamePropertyReference forgedReference;
var parser = new PropertyParser();
var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan()));
if(parsedPropertyName.owner == null)
forgedReference = new XamlIlAstNamePropertyReference(lineInfo, selectorTypeReference,
propertyName, selectorTypeReference);
else
{
var xmlOwner = parsedPropertyName.ns;
if (xmlOwner != null)
xmlOwner += ":";
xmlOwner += parsedPropertyName.owner;
var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true);
forgedReference = new XamlIlAstNamePropertyReference(lineInfo,
tref, parsedPropertyName.name, tref);
}
var clrProperty =
((XamlIlAstClrProperty)new XamlIlPropertyReferenceResolver().Transform(context,
forgedReference));
return new XamlIlAvaloniaPropertyNode(lineInfo,
context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"),
clrProperty);
}
}
class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode
{
public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, XamlIlAstClrProperty property) : base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(this, type, false);
Property = property;
}
public XamlIlAstClrProperty Property { get; }
public IXamlIlAstTypeReference Type { get; }
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
}
}
interface IXamlIlAvaloniaProperty
{
IXamlIlField AvaloniaProperty { get; }
}
class XamlIlAvaloniaProperty : XamlIlAstClrProperty, IXamlIlAvaloniaProperty
{
public IXamlIlField AvaloniaProperty { get; }
public XamlIlAvaloniaProperty(XamlIlAstClrProperty original, IXamlIlField field,
AvaloniaXamlIlWellKnownTypes types)
:base(original, original.Name, original.DeclaringType, original.Getter, original.Setters)
{
AvaloniaProperty = field;
CustomAttributes = original.CustomAttributes;
if (!original.CustomAttributes.Any(ca => ca.Type.Equals(types.AssignBindingAttribute)))
Setters.Insert(0, new BindingSetter(types, original.DeclaringType, field));
Setters.Insert(0, new UnsetValueSetter(types, original.DeclaringType, field));
}
abstract class AvaloniaPropertyCustomSetter : IXamlIlPropertySetter
{
protected AvaloniaXamlIlWellKnownTypes Types;
protected IXamlIlField AvaloniaProperty;
public AvaloniaPropertyCustomSetter(AvaloniaXamlIlWellKnownTypes types,
IXamlIlType declaringType,
IXamlIlField avaloniaProperty)
{
Types = types;
AvaloniaProperty = avaloniaProperty;
TargetType = declaringType;
}
public IXamlIlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters
{
AllowXNull = false
};
public IReadOnlyList<IXamlIlType> Parameters { get; set; }
public abstract void Emit(IXamlIlEmitter codegen);
}
class BindingSetter : AvaloniaPropertyCustomSetter
{
public BindingSetter(AvaloniaXamlIlWellKnownTypes types,
IXamlIlType declaringType,
IXamlIlField avaloniaProperty) : base(types, declaringType, avaloniaProperty)
{
Parameters = new[] {types.IBinding};
}
public override void Emit(IXamlIlEmitter emitter)
{
using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding))
emitter
.Stloc(bloc.Local)
.Ldsfld(AvaloniaProperty)
.Ldloc(bloc.Local)
// TODO: provide anchor?
.Ldnull();
emitter.EmitCall(Types.AvaloniaObjectBindMethod, true);
}
}
class UnsetValueSetter : AvaloniaPropertyCustomSetter
{
public UnsetValueSetter(AvaloniaXamlIlWellKnownTypes types, IXamlIlType declaringType, IXamlIlField avaloniaProperty)
: base(types, declaringType, avaloniaProperty)
{
Parameters = new[] {types.UnsetValueType};
}
public override void Emit(IXamlIlEmitter codegen)
{
var unsetValue = Types.AvaloniaProperty.Fields.First(f => f.Name == "UnsetValue");
codegen
// Ignore the instance and load one from the static field to avoid extra local variable
.Pop()
.Ldsfld(AvaloniaProperty)
.Ldsfld(unsetValue)
.Ldc_I4(0)
.EmitCall(Types.AvaloniaObjectSetValueMethod, true);
}
}
}
}

9
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlParentStackProvider.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public interface IAvaloniaXamlIlParentStackProvider
{
IEnumerable<object> Parents { get; }
}
}

15
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public interface IAvaloniaXamlIlXmlNamespaceInfoProvider
{
IReadOnlyDictionary<string, IReadOnlyList<AvaloniaXamlIlXmlNamespaceInfo>> XmlNamespaces { get; }
}
public class AvaloniaXamlIlXmlNamespaceInfo
{
public string ClrNamespace { get; set; }
public string ClrAssemblyName { get; set; }
}
}

148
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data;
using Portable.Xaml.Markup;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global
namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public static class XamlIlRuntimeHelpers
{
public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList();
return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes));
}
class DeferredParentServiceProvider : IAvaloniaXamlIlParentStackProvider, IServiceProvider
{
private readonly IServiceProvider _parentProvider;
private readonly List<IResourceNode> _parentResourceNodes;
public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes)
{
_parentProvider = parentProvider;
_parentResourceNodes = parentResourceNodes;
}
public IEnumerable<object> Parents => GetParents();
IEnumerable<object> GetParents()
{
if(_parentResourceNodes == null)
yield break;
foreach (var p in _parentResourceNodes)
yield return p;
}
public object GetService(Type serviceType)
{
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
return _parentProvider?.GetService(serviceType);
}
}
public static void ApplyNonMatchingMarkupExtensionV1(object target, object property, IServiceProvider prov,
object value)
{
if (value is IBinding b)
{
if (property is AvaloniaProperty p)
((AvaloniaObject)target).Bind(p, b);
else
throw new ArgumentException("Attempt to apply binding to non-avalonia property " + property);
}
else if (value is UnsetValueType unset)
{
if (property is AvaloniaProperty p)
((AvaloniaObject)target).SetValue(p, unset);
//TODO: Investigate
//throw new ArgumentException("Attempt to apply UnsetValue to non-avalonia property " + property);
}
else
throw new ArgumentException("Don't know what to do with " + value.GetType());
}
public static IServiceProvider CreateInnerServiceProviderV1(IServiceProvider compiled)
=> new InnerServiceProvider(compiled);
class InnerServiceProvider : IServiceProvider
{
private readonly IServiceProvider _compiledProvider;
private XamlTypeResolver _resolver;
public InnerServiceProvider(IServiceProvider compiledProvider)
{
_compiledProvider = compiledProvider;
}
public object GetService(Type serviceType)
{
if (serviceType == typeof(IXamlTypeResolver))
return _resolver ?? (_resolver = new XamlTypeResolver(
_compiledProvider.GetService<IAvaloniaXamlIlXmlNamespaceInfoProvider>()));
return null;
}
}
class XamlTypeResolver : IXamlTypeResolver
{
private readonly IAvaloniaXamlIlXmlNamespaceInfoProvider _nsInfo;
public XamlTypeResolver(IAvaloniaXamlIlXmlNamespaceInfoProvider nsInfo)
{
_nsInfo = nsInfo;
}
public Type Resolve(string qualifiedTypeName)
{
var sp = qualifiedTypeName.Split(new[] {':'}, 2);
var (ns, name) = sp.Length == 1 ? ("", qualifiedTypeName) : (sp[0], sp[1]);
var namespaces = _nsInfo.XmlNamespaces;
var dic = (Dictionary<string, IReadOnlyList<AvaloniaXamlIlXmlNamespaceInfo>>)namespaces;
if (!namespaces.TryGetValue(ns, out var lst))
throw new ArgumentException("Unable to resolve namespace for type " + qualifiedTypeName);
foreach (var entry in lst)
{
var asm = Assembly.Load(new AssemblyName(entry.ClrAssemblyName));
var resolved = asm.GetType(entry.ClrNamespace + "." + name);
if (resolved != null)
return resolved;
}
throw new ArgumentException(
$"Unable to resolve type {qualifiedTypeName} from any of the following locations: " +
string.Join(",", lst.Select(e => $"`{e.ClrAssemblyName}:{e.ClrNamespace}.{name}`")));
}
}
public static readonly IServiceProvider RootServiceProviderV1 = new RootServiceProvider();
class RootServiceProvider : IServiceProvider, IAvaloniaXamlIlParentStackProvider
{
public object GetService(Type serviceType)
{
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
return null;
}
public IEnumerable<object> Parents
{
get
{
if (Application.Current != null)
yield return Application.Current;
}
}
}
}
}

1
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -0,0 +1 @@
Subproject commit 3b3c1f93a566080d417b9782f9cc4ea67cd62344

2
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@ -19,7 +19,7 @@ namespace Avalonia.Data
/// <summary>
/// Gets the collection of child bindings.
/// </summary>
[Content]
[Content, AssignBinding]
public IList<IBinding> Bindings { get; set; } = new List<IBinding>();
/// <summary>

2
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -183,5 +183,7 @@ namespace Avalonia.Data
PublishValue();
}
}
public IBinding ProvideValue() => this;
}
}

2
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -275,7 +275,7 @@ namespace Avalonia.Markup.Parsers
private static TSyntax ParseType<TSyntax>(ref CharacterReader r, TSyntax syntax)
where TSyntax : ITypeSyntax
{
ReadOnlySpan<char> ns = null;
ReadOnlySpan<char> ns = default;
ReadOnlySpan<char> type;
var namespaceOrTypeName = r.ParseIdentifier();

7
src/Shared/PlatformSupport/AssetLoader.cs

@ -97,6 +97,13 @@ namespace Avalonia.Shared.PlatformSupport
return (asset.GetStream(), asset.Assembly);
}
public Assembly GetAssembly(Uri uri, Uri baseUri)
{
if (!uri.IsAbsoluteUri && baseUri != null)
uri = new Uri(baseUri, uri);
return GetAssembly(uri).Assembly;
}
/// <summary>
/// Gets all assets of a folder and subfolders that match specified uri.
/// </summary>

7
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -29,13 +29,10 @@
<EmbeddedResource Include="Xaml\Style1.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Xaml\Style2.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml"/>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

2
tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs

@ -19,7 +19,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
{
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{
var xaml = "<Style xmlns='https://github.com/avaloniaui'><Setter Value='{Binding}'/></Style>";
var xaml = "<Style Selector='Button' xmlns='https://github.com/avaloniaui'><Setter Property='Content' Value='{Binding}'/></Style>";
var loader = new AvaloniaXamlLoader();
var style = (Style)loader.Load(xaml);
var setter = (Setter)(style.Setters.First());

22
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
@ -17,6 +18,7 @@ using Portable.Xaml;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using System.Xml;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
@ -48,6 +50,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
[Fact]
public void AvaloniaProperty_Without_Getter_And_Setter_Is_Set()
{
// It's not possible to know in compile time if a read-only property has a magic way of being not read-only
if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
var xaml =
@"<local:NonControl xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'
@ -61,6 +66,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
[Fact]
public void AvaloniaProperty_With_Getter_And_No_Setter_Is_Set()
{
if(!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
var xaml =
@"<local:NonControl xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'
@ -142,23 +149,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal("Foo", ToolTip.GetTip(target));
}
[Fact]
public void NonExistent_Property_Throws()
{
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' DoesntExist='foo'/>";
Assert.Throws<XamlObjectWriterException>(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
}
[Fact]
public void Non_Attached_Property_With_Attached_Property_Syntax_Throws()
{
// 1) It has been allowed in AvaloniaObject.SetValue for ages
// 2) There is no way to know if AddOwner was called in compile-time
if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' TextBlock.Text='foo'/>";
Assert.Throws<XamlObjectWriterException>(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
}
[Fact]
@ -518,7 +529,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var xaml = @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:sys='clr-namespace:System;assembly=mscorlib'>
xmlns:sys='clr-namespace:System;assembly=netstandard'>
<Style.Resources>
<SolidColorBrush x:Key='Brush'>White</SolidColorBrush>
<sys:Double x:Key='Double'>10</sys:Double>
@ -587,6 +598,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
[Fact]
public void Xaml_Binding_Is_Delayed()
{
if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var xaml =

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=mscorlib'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.DataTemplates>
<DataTemplate DataType='{x:Type sys:String}'>

10
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs

@ -4,6 +4,7 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Portable.Xaml;
using Xunit;
@ -31,12 +32,15 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var loader = new AvaloniaXamlLoader();
var target = new MyButton();
Assert.Throws<XamlObjectWriterException>(() => loader.Load(xaml, rootInstance: target));
XamlTestHelpers.AssertThrowsXamlException(() => loader.Load(xaml, rootInstance: target));
}
[Fact]
public void Exception_Is_Not_Thrown_If_Event_Not_Found_In_Design_Mode()
{
// Runtime compiler should properly understand x:Class
if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
return;
var xaml = @"<Button xmlns='https://github.com/avaloniaui' Click='NotFound'/>";
var loader = new AvaloniaXamlLoader { IsDesignMode = true };
var target = new MyButton();
@ -53,11 +57,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
});
}
class MyButton : Button
public class MyButton : Button
{
public bool Clicked { get; private set; }
public void OnClick(object sender, EventArgs e)
public void OnClick(object sender, RoutedEventArgs e)
{
Clicked = true;
}

5
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml

@ -1,8 +1,9 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Precompile="False">
<Style.Resources>
<Color x:Key="Red">Red</Color>
<Color x:Key="Green">Green</Color>
<Color x:Key="Blue">Blue</Color>
</Style.Resources>
</Style>
</Style>

5
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml

@ -1,8 +1,9 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Precompile="False">
<Style.Resources>
<SolidColorBrush x:Key="RedBrush" Color="{DynamicResource Red}"/>
<SolidColorBrush x:Key="GreenBrush" Color="{DynamicResource Green}"/>
<SolidColorBrush x:Key="BlueBrush" Color="{DynamicResource Blue}"/>
</Style.Resources>
</Style>
</Style>

6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithPrecompiledXaml.xaml

@ -0,0 +1,6 @@
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlClassWithPrecompiledXaml'
Background="Red">
</UserControl>

146
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Media;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using JetBrains.Annotations;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests
{
public class XamlIlTests
{
[Fact]
public void Binding_Button_IsPressed_ShouldWork()
{
var parsed = (Button)AvaloniaXamlLoader.Parse(@"
<Button xmlns='https://github.com/avaloniaui' IsPressed='{Binding IsPressed, Mode=TwoWay}' />");
var ctx = new XamlIlBugTestsDataContext();
parsed.DataContext = ctx;
parsed.SetValue(Button.IsPressedProperty, true);
Assert.True(ctx.IsPressed);
}
[Fact]
public void Transitions_Should_Be_Properly_Parsed()
{
var parsed = (Grid)AvaloniaXamlLoader.Parse(@"
<Grid xmlns='https://github.com/avaloniaui' >
<Grid.Transitions>
<DoubleTransition Property='Opacity'
Easing='CircularEaseIn'
Duration='0:0:0.5' />
</Grid.Transitions>
</Grid>");
Assert.Equal(1, parsed.Transitions.Count);
Assert.Equal(Visual.OpacityProperty, parsed.Transitions[0].Property);
}
[Fact]
public void Parser_Should_Override_Precompiled_Xaml()
{
var precompiled = new XamlIlClassWithPrecompiledXaml();
Assert.Equal(Brushes.Red, precompiled.Background);
Assert.Equal(1, precompiled.Opacity);
var loaded = (XamlIlClassWithPrecompiledXaml)AvaloniaXamlLoader.Parse(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlClassWithPrecompiledXaml'
Opacity='0'>
</UserControl>");
Assert.Equal(loaded.Opacity, 0);
Assert.Null(loaded.Background);
}
[Fact]
public void RelativeSource_TemplatedParent_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
new AvaloniaXamlLoader().Load(@"
<Application
xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests;assembly=Avalonia.Markup.Xaml.UnitTests'
>
<Application.Styles>
<Style Selector='Button'>
<Setter Property='Template'>
<ControlTemplate>
<Grid><Grid><Grid>
<Canvas>
<Canvas.Background>
<SolidColorBrush>
<SolidColorBrush.Color>
<MultiBinding>
<MultiBinding.Converter>
<local:XamlIlBugTestsBrushToColorConverter/>
</MultiBinding.Converter>
<Binding Path='Background' RelativeSource='{RelativeSource TemplatedParent}'/>
<Binding Path='Background' RelativeSource='{RelativeSource TemplatedParent}'/>
<Binding Path='Background' RelativeSource='{RelativeSource TemplatedParent}'/>
</MultiBinding>
</SolidColorBrush.Color>
</SolidColorBrush>
</Canvas.Background>
</Canvas>
</Grid></Grid></Grid>
</ControlTemplate>
</Setter>
</Style>
</Application.Styles>
</Application>",
null, Application.Current);
var parsed = (Window)AvaloniaXamlLoader.Parse(@"
<Window
xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests;assembly=Avalonia.Markup.Xaml.UnitTests'
>
<Button Background='Red' />
</Window>
");
var btn = ((Button)parsed.Content);
btn.ApplyTemplate();
var canvas = (Canvas)btn.GetVisualChildren().First()
.VisualChildren.First()
.VisualChildren.First()
.VisualChildren.First();
Assert.Equal(Brushes.Red.Color, ((ISolidColorBrush)canvas.Background).Color);
}
}
}
public class XamlIlBugTestsBrushToColorConverter : IMultiValueConverter
{
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
return (values[0] as ISolidColorBrush)?.Color;
}
}
public class XamlIlBugTestsDataContext : INotifyPropertyChanged
{
public bool IsPressed { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class XamlIlClassWithPrecompiledXaml : UserControl
{
}
}

23
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs

@ -0,0 +1,23 @@
using System;
using System.Xml;
using Portable.Xaml;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class XamlTestHelpers
{
public static void AssertThrowsXamlException(Action cb)
{
try
{
cb();
}
catch (Exception e)
{
if(e is XamlObjectWriterException || e is XmlException)
return;
}
throw new Exception("Expected to throw xaml exception");
}
}
}

5
tests/Avalonia.UnitTests/MockAssetLoader.cs

@ -32,6 +32,11 @@ namespace Avalonia.UnitTests
return (Open(uri, baseUri), (Assembly)null);
}
public Assembly GetAssembly(Uri uri, Uri baseUri = null)
{
return null;
}
public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
{
return _assets.Keys.Where(x => x.AbsolutePath.Contains(uri.AbsolutePath));

5
tests/Avalonia.UnitTests/UnitTestApplication.cs

@ -19,6 +19,11 @@ namespace Avalonia.UnitTests
{
private readonly TestServices _services;
public UnitTestApplication() : this(null)
{
}
public UnitTestApplication(TestServices services)
{
_services = services ?? new TestServices();

Loading…
Cancel
Save