summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Build.Tasks/CompiledMarkupExtensions/StaticExtension.cs
blob: 014fb827dba346eda90e36b6acd247cf5f0f8329 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
using System.Collections.Generic;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Xamarin.Forms.Xaml;
using System.Xml;

using static System.String;

namespace Xamarin.Forms.Build.Tasks
{
	class StaticExtension : ICompiledMarkupExtension
	{
		public IEnumerable<Instruction> ProvideValue(IElementNode node, ModuleDefinition module, ILContext context, out TypeReference memberRef)
		{
			INode ntype;
			if (!node.Properties.TryGetValue(new XmlName("", "Member"), out ntype))
				ntype = node.CollectionItems[0];
			var member = ((ValueNode)ntype).Value as string;

			if (IsNullOrEmpty(member) || !member.Contains(".")) {
				var lineInfo = node as IXmlLineInfo;
				throw new XamlParseException("Syntax for x:Static is [Member=][prefix:]typeName.staticMemberName", lineInfo);
			}

			var dotIdx = member.LastIndexOf('.');
			var typename = member.Substring(0, dotIdx);
			var membername = member.Substring(dotIdx + 1);

			var typeRef = GetTypeReference(typename, module, node);
			var fieldRef = GetFieldReference(typeRef, membername, module);
			var propertyDef = GetPropertyDefinition(typeRef, membername, module);

			if (fieldRef == null && propertyDef == null)
				throw new XamlParseException($"x:Static: unable to find a public static field, static property, const or enum value named {membername} in {typename}", node as IXmlLineInfo);

			var fieldDef = fieldRef?.Resolve();
			if (fieldRef != null) {
				memberRef = fieldRef.FieldType;
				if (!fieldDef.HasConstant)
					return new [] { Instruction.Create(OpCodes.Ldsfld, fieldRef) };

				//Constants can be numbers, Boolean values, strings, or a null reference. (https://msdn.microsoft.com/en-us/library/e6w8fe1b.aspx)
				if (memberRef == module.TypeSystem.Boolean)
					return new [] { Instruction.Create(((bool)fieldDef.Constant) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0) };
				if (memberRef == module.TypeSystem.String)
					return new [] { Instruction.Create(OpCodes.Ldstr, (string)fieldDef.Constant) };
				if (fieldDef.Constant == null)
					return new [] { Instruction.Create(OpCodes.Ldnull) };
				if (memberRef == module.TypeSystem.Char)
					return new [] { Instruction.Create(OpCodes.Ldc_I4, (char)fieldDef.Constant) };
				if (memberRef == module.TypeSystem.Single)
					return new [] { Instruction.Create(OpCodes.Ldc_R4, (float)fieldDef.Constant) };
				if (memberRef == module.TypeSystem.Double)
					return new [] { Instruction.Create(OpCodes.Ldc_R8, (double)fieldDef.Constant) };
				if (memberRef == module.TypeSystem.Byte || memberRef == module.TypeSystem.Int16 || memberRef == module.TypeSystem.Int32)
					return new [] { Instruction.Create(OpCodes.Ldc_I4, (int)fieldDef.Constant) };
				if (memberRef == module.TypeSystem.SByte || memberRef == module.TypeSystem.UInt16 || memberRef == module.TypeSystem.UInt32)
					return new [] { Instruction.Create(OpCodes.Ldc_I4, (uint)fieldDef.Constant) };
				if (memberRef == module.TypeSystem.Int64)
					return new [] { Instruction.Create(OpCodes.Ldc_I8, (long)fieldDef.Constant) };
				if (memberRef == module.TypeSystem.UInt64)
					return new [] { Instruction.Create(OpCodes.Ldc_I8, (ulong)fieldDef.Constant) };

				//enum values
				if (memberRef.Resolve().IsEnum) {
					if (fieldDef.Constant is long)
						return new [] { Instruction.Create(OpCodes.Ldc_I8, (long)fieldDef.Constant) };
					if (fieldDef.Constant is ulong)
						return new [] { Instruction.Create(OpCodes.Ldc_I8, (ulong)fieldDef.Constant) };
					if (fieldDef.Constant is uint)
						return new [] { Instruction.Create(OpCodes.Ldc_I4, (uint)fieldDef.Constant) };
					//everything else will cast just fine to an int
					return new [] { Instruction.Create(OpCodes.Ldc_I4, (int)fieldDef.Constant) };
				}
			}

			memberRef = propertyDef.PropertyType;
			var getterDef = propertyDef.GetMethod;
			return new [] { Instruction.Create(OpCodes.Call, getterDef) };
		}


		public static TypeReference GetTypeReference(string xmlType, ModuleDefinition module, IElementNode node)
		{
			var split = xmlType.Split(':');
			if (split.Length > 2)
				throw new XamlParseException($"Type \"{xmlType}\" is invalid", node as IXmlLineInfo);

			string prefix, name;
			if (split.Length == 2) {
				prefix = split [0];
				name = split [1];
			} else {
				prefix = "";
				name = split [0];
			}
			var namespaceuri = node.NamespaceResolver.LookupNamespace(prefix) ?? "";
			return XmlTypeExtensions.GetTypeReference(new XmlType(namespaceuri, name, null), module, node as IXmlLineInfo);
		}

		public static FieldReference GetFieldReference(TypeReference typeRef, string fieldName, ModuleDefinition module)
		{
			TypeReference declaringTypeReference;
			FieldReference fRef = typeRef.GetField(fd => fd.Name == fieldName &&
			                                       fd.IsStatic &&
			                                       fd.IsPublic, out declaringTypeReference);
			if (fRef != null) {
				fRef = module.Import(fRef.ResolveGenericParameters(declaringTypeReference));
				fRef.FieldType = module.Import(fRef.FieldType);
			}
			return fRef;
		}

		public static PropertyDefinition GetPropertyDefinition(TypeReference typeRef, string propertyName, ModuleDefinition module)
		{
			TypeReference declaringTypeReference;
			PropertyDefinition pDef = typeRef.GetProperty(pd => pd.Name == propertyName &&
			                                              pd.GetMethod.IsPublic &&
			                                              pd.GetMethod.IsStatic, out declaringTypeReference);
			return pDef;
		}
	}
}