-
Notifications
You must be signed in to change notification settings - Fork 774
Expand file tree
/
Copy pathTupleCodecs.cs
More file actions
135 lines (115 loc) · 4.82 KB
/
TupleCodecs.cs
File metadata and controls
135 lines (115 loc) · 4.82 KB
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
124
125
126
127
128
129
130
131
132
133
134
135
namespace Python.Runtime.Codecs
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public sealed class TupleCodec<TTuple> : IPyObjectEncoder, IPyObjectDecoder
{
TupleCodec() { }
public static TupleCodec<TTuple> Instance { get; } = new TupleCodec<TTuple>();
public bool CanEncode(Type type)
{
if (type == typeof(object) || type == typeof(TTuple)) return true;
return type.Namespace == typeof(TTuple).Namespace
// generic versions of tuples are named Tuple`TYPE_ARG_COUNT
&& type.Name.StartsWith(typeof(TTuple).Name + '`');
}
public PyObject? TryEncode(object value)
{
if (value == null) return null;
var tupleType = value.GetType();
if (tupleType == typeof(object)) return null;
if (!this.CanEncode(tupleType)) return null;
if (tupleType == typeof(TTuple)) return new PyTuple();
nint fieldCount = tupleType.GetGenericArguments().Length;
using var tuple = Runtime.PyTuple_New(fieldCount);
PythonException.ThrowIfIsNull(tuple);
int fieldIndex = 0;
foreach (FieldInfo field in tupleType.GetFields())
{
var item = field.GetValue(value);
using var pyItem = Converter.ToPython(item, field.FieldType);
Runtime.PyTuple_SetItem(tuple.Borrow(), fieldIndex, pyItem.Steal());
fieldIndex++;
}
return new PyTuple(tuple.Steal());
}
public bool CanDecode(PyType objectType, Type targetType)
=> PythonReferenceComparer.Instance.Equals(objectType, Runtime.PyTupleType)
&& this.CanEncode(targetType);
public bool TryDecode<T>(PyObject pyObj, out T? value)
{
if (pyObj == null) throw new ArgumentNullException(nameof(pyObj));
value = default;
if (!Runtime.PyTuple_Check(pyObj)) return false;
if (typeof(T) == typeof(object))
{
bool converted = Decode(pyObj, out object? result);
if (converted)
{
value = (T?)result;
return true;
}
return false;
}
var itemTypes = typeof(T).GetGenericArguments();
nint itemCount = Runtime.PyTuple_Size(pyObj);
if (itemTypes.Length != itemCount) return false;
if (itemCount == 0)
{
value = (T)EmptyTuple;
return true;
}
var elements = new object?[itemCount];
for (int itemIndex = 0; itemIndex < itemTypes.Length; itemIndex++)
{
BorrowedReference pyItem = Runtime.PyTuple_GetItem(pyObj, itemIndex);
if (!Converter.ToManaged(pyItem, itemTypes[itemIndex], out elements[itemIndex], setError: false))
{
Exceptions.Clear();
return false;
}
}
var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes);
value = (T)factory.Invoke(null, elements);
return true;
}
static bool Decode(PyObject tuple, out object? value)
{
long itemCount = Runtime.PyTuple_Size(tuple);
if (itemCount == 0)
{
value = EmptyTuple;
return true;
}
var elements = new object?[itemCount];
var itemTypes = new Type[itemCount];
value = null;
for (int itemIndex = 0; itemIndex < elements.Length; itemIndex++)
{
var pyItem = Runtime.PyTuple_GetItem(tuple, itemIndex);
if (!Converter.ToManaged(pyItem, typeof(object), out elements[itemIndex], setError: false))
{
Exceptions.Clear();
return false;
}
itemTypes[itemIndex] = elements[itemIndex]?.GetType() ?? typeof(object);
}
var factory = tupleCreate[itemCount].MakeGenericMethod(itemTypes);
value = factory.Invoke(null, elements);
return true;
}
static readonly MethodInfo[] tupleCreate =
typeof(TTuple).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => m.Name == nameof(Tuple.Create))
.OrderBy(m => m.GetParameters().Length)
.ToArray();
static readonly object EmptyTuple = tupleCreate[0].Invoke(null, parameters: new object[0]);
public static void Register()
{
PyObjectConversions.RegisterEncoder(Instance);
PyObjectConversions.RegisterDecoder(Instance);
}
}
}