committed by
GitHub
99 changed files with 3434 additions and 164 deletions
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Metadata |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Class)] |
|||
public class UsableDuringInitializationAttribute : Attribute |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
|
|||
} |
|||
@ -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 |
|||
}); |
|||
|
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -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); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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); |
|||
|
|||
} |
|||
|
|||
|
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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")); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.Runtime |
|||
{ |
|||
public interface IAvaloniaXamlIlParentStackProvider |
|||
{ |
|||
IEnumerable<object> Parents { get; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
Subproject commit 3b3c1f93a566080d417b9782f9cc4ea67cd62344 |
|||
@ -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> |
|||
|
|||
@ -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> |
|||
|
|||
@ -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> |
|||
@ -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 |
|||
{ |
|||
} |
|||
|
|||
} |
|||
@ -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"); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue