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" |
<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> |
<Style.Resources> |
||||
<Color x:Key="Red">Red</Color> |
<Color x:Key="Red">Red</Color> |
||||
<Color x:Key="Green">Green</Color> |
<Color x:Key="Green">Green</Color> |
||||
<Color x:Key="Blue">Blue</Color> |
<Color x:Key="Blue">Blue</Color> |
||||
</Style.Resources> |
</Style.Resources> |
||||
</Style> |
</Style> |
||||
|
|||||
@ -1,8 +1,9 @@ |
|||||
<Style xmlns="https://github.com/avaloniaui" |
<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> |
<Style.Resources> |
||||
<SolidColorBrush x:Key="RedBrush" Color="{DynamicResource Red}"/> |
<SolidColorBrush x:Key="RedBrush" Color="{DynamicResource Red}"/> |
||||
<SolidColorBrush x:Key="GreenBrush" Color="{DynamicResource Green}"/> |
<SolidColorBrush x:Key="GreenBrush" Color="{DynamicResource Green}"/> |
||||
<SolidColorBrush x:Key="BlueBrush" Color="{DynamicResource Blue}"/> |
<SolidColorBrush x:Key="BlueBrush" Color="{DynamicResource Blue}"/> |
||||
</Style.Resources> |
</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