8000 Match generic and private methods upon runtime reload · pythonnet/pythonnet@332cae9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 332cae9

Browse files
committed
Match generic and private methods upon runtime reload
1 parent 640e97b commit 332cae9

File tree

5 files changed

+160
-63
lines changed

5 files changed

+160
-63
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ Instead, `PyIterable` does that.
103103
- Providing an invalid type parameter to a generic type or method produces a helpful Python error
104104
- Empty parameter names (as can be generated from F#) do not cause crashes
105105
- Unicode strings with surrogates were truncated when converting from Python
106+
- `Reload` mode now supports generic methods (previously Python would stop seeing them after reload)
106107

107108
### Removed
108109

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.IO;
2+
using System.Reflection;
3+
4+
using NUnit.Framework;
5+
6+
using Python.Runtime;
7+
8+
namespace Python.EmbeddingTest.StateSerialization;
9+
10+
public class MethodSerialization
11+
{
12+
[Test]
13+
public void GenericRoundtrip()
14+
{
15+
var method = typeof(MethodTestHost).GetMethod(nameof(MethodTestHost.Generic));
16+
var maybeMethod = new MaybeMethodBase<MethodBase>(method);
17+
var restored = SerializationRoundtrip(maybeMethod);
18+
Assert.IsTrue(restored.Valid);
19+
Assert.AreEqual(method, restored.Value);
20+
}
21+
22+
static T SerializationRoundtrip<T>(T item)
23+
{
24+
using var buf = new MemoryStream();
25+
var formatter = RuntimeData.CreateFormatter();
26+
formatter.Serialize(buf, A93C item);
27+
buf.Position = 0;
28+
return (T)formatter.Deserialize(buf);
29+
}
30+
}
31+
32+
public class MethodTestHost
33+
{
34+
public void Generic<T>(T item, T[] array, ref T @ref) { }
35+
}

src/runtime/Reflection/ParameterHelper.cs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
35
using System.Reflection;
46

57
namespace Python.Runtime.Reflection;
68

79
[Serializable]
8-
struct ParameterHelper : IEquatable<ParameterInfo>
10+
class ParameterHelper : IEquatable<ParameterInfo>
911
{
1012
public readonly string TypeName;
1113
public readonly ParameterModifier Modifier;
14+
public readonly ParameterHelper[]? GenericArguments;
1215

13-
public ParameterHelper(ParameterInfo tp)
16+
public ParameterHelper(ParameterInfo tp) : this(tp.ParameterType)
1417
{
15-
TypeName = tp.ParameterType.AssemblyQualifiedName;
1618
Modifier = ParameterModifier.None;
1719

1820
if (tp.IsIn && tp.ParameterType.IsByRef)
@@ -29,12 +31,55 @@ public ParameterHelper(ParameterInfo tp)
2931
}
3032
}
3133

34+
public ParameterHelper(Type type)
35+
{
36+
TypeName = type.AssemblyQualifiedName;
37+
if (TypeName is null)
38+
{
39+
if (type.IsByRef || type.IsArray)
40+
{
41+
TypeName = type.IsArray ? "[]" : "&";
42+
GenericArguments = new[] { new ParameterHelper(type.GetElementType()) };
43+
}
44+
else
45+
{
46+
Debug.Assert(type.ContainsGenericParameters);
47+
TypeName = $"{type.Assembly}::{type.Namespace}/{type.Name}";
48+
GenericArguments = type.GenericTypeArguments.Select(t => new ParameterHelper(t)).ToArray();
49+
}
50+
}
51+
}
52+
53+
public bool IsSpecialType => TypeName == "&" || TypeName == "[]";
54+
3255
public bool Equals(ParameterInfo other)
3356
{
3457
return this.Equals(new ParameterHelper(other));
3558
}
3659

3760
public bool Matches(ParameterInfo other) => this.Equals(other);
61+
62+
public bool Equals(ParameterHelper other)
63+
{
64+
if (other is null) return false;
65+
66+
if (!(other.TypeName == TypeName && other.Modifier == Modifier))
67+
return false;
68+
69+
if (GenericArguments == other.GenericArguments) return true;
70+
71+
if (GenericArguments is not null && other.GenericArguments is not null)
72+
{
73+
if (GenericArguments.Length != other.GenericArguments.Length) return false;
74+
for (int arg = 0; arg < GenericArguments.Length; arg++)
75+
{
76+
if (!GenericArguments[arg].Equals(other.GenericArguments[arg])) return false;
77+
}
78+
return true;
79+
}
80+
81+
return false;
82+
}
3883
}
3984

4085
enum ParameterModifier

src/runtime/StateSerialization/MaybeMethodBase.cs

Lines changed: 75 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Reflection;
45
using System.Runtime.Serialization;
@@ -17,8 +18,9 @@ internal struct MaybeMethodBase<T> : ISerializable where T: MethodBase
1718
const string SerializationType = "t";
1819
// Fhe parameters of the MethodBase
1920
const string SerializationParameters = "p";
20-
const string SerializationIsCtor = "c";
2121
const string SerializationMethodName = "n";
22+
const string SerializationGenericParamCount = "G";
23+
const string SerializationFlags = "V";
2224

2325
public static implicit operator MaybeMethodBase<T> (T? ob) => new (ob);
2426

@@ -62,6 +64,7 @@ public MaybeMethodBase(T? mi)
6264
{
6365
info = mi;
6466
name = mi?.ToString();
67+
Debug.Assert(name != null || info == null);
6568
deserializationException = null;
6669
}
6770

@@ -82,75 +85,60 @@ internal MaybeMethodBase(SerializationInfo serializationInfo, StreamingContext c
8285
{
8386
throw new SerializationException($"The underlying type {typeName} can't be found");
8487
}
88+
89+
var flags = (MaybeMethodFlags)serializationInfo.GetInt32(SerializationFlags);
90+
int genericCount = serializationInfo.GetInt32(SerializationGenericParamCount);
91+
8592
// Get the method's parameters types
8693
var field_name = serializationInfo.GetString(SerializationMethodName);
8794
var param = (ParameterHelper[])serializationInfo.GetValue(SerializationParameters, typeof(ParameterHelper[]));
88-
Type[] types = new Type[param.Length];
89-
bool hasRefType = false;
90-
for (int i = 0; i < param.Length; i++)
91-
{
92-
var paramTypeName = param[i].TypeName;
93-
types[i] = Type.GetType(paramTypeName);
94-
if (types[i] == null)
95-
{
96-
throw new SerializationException($"The parameter of type {paramTypeName} can't be found");
97-
}
98-
else if (types[i].IsByRef)
99-
{
100-
hasRefType = true;
101-
}
102-
}
10395

104-
MethodBase? mb = null;
105-
if (serializationInfo.GetBoolean(SerializationIsCtor))
106-
{
107-
// We never want the static constructor.
108-
mb = tp.GetConstructor(ClassManager.BindingFlags&(~BindingFlags.Static), binder:null, types:types, modifiers:null);
109-
}
110-
else
111-
{
112-
mb = tp.GetMethod(field_name, ClassManager.Bindi F438 ngFlags, binder:null, types:types, modifiers:null);
113-
}
114-
115-
if (mb != null && hasRefType)
116-
{
117-
mb = CheckRefTypes(mb, param);
118-
}
119-
120-
// Do like in ClassManager.GetClassInfo
121-
if(mb != null && ClassManager.ShouldBindMethod(mb))
122-
{
123-
info = mb;
124-
}
96+
info = ScanForMethod(tp, field_name, genericCount, flags, param);
12597
}
12698
catch (Exception e)
12799
{
128100
deserializationException = e;
129101
}
130102
}
131103

132-
MethodBase? CheckRefTypes(MethodBase mb, ParameterHelper[] ph)
104+
static MethodBase ScanForMethod(Type declaringType, string name, int genericCount, MaybeMethodFlags flags, ParameterHelper[] parameters)
133105
{
134-
// One more step: Changing:
135-
// void MyFn (ref int a)
136-
// to:
137-
// void MyFn (out int a)
138-
// will still find the function correctly as, `in`, `out` and `ref`
139-
// are all represented as a reference type. Query the method we got
140-
// and validate the parameters
141-
if (ph.Length != 0)
142-
{
143-
foreach (var item in Enumerable.Zip(ph, mb.GetParameters(), (orig, current) => new {orig, current}))
144-
{
145-
if (!item.current.Equals(item.orig))
146-
{
147-
// False positive
148-
return null;
149-
}
150-
}
151-
}
106+
var bindingFlags = ClassManager.BindingFlags;
107+
if (flags.HasFlag(MaybeMethodFlags.Constructor)) bindingFlags &= ~BindingFlags.Static;
152108

153-
return mb;
109+
var alternatives = declaringType.GetMember(name,
110+
flags.HasFlag(MaybeMethodFlags.Constructor)
111+
? MemberTypes.Constructor
112+
: MemberTypes.Method,
113+
bindingFlags);
114+
115+
if (alternatives.Length == 0)
116+
throw new MissingMethodException($"{declaringType}.{name}");
117+
118+
var visibility = flags & MaybeMethodFlags.Visibility;
119+
120+
var result = alternatives.Cast<MethodBase>().FirstOrDefault(m
121+
=> MatchesGenericCount(m, genericCount) && MatchesSignature(m, parameters)
122+
&& (Visibility(m) == visibility || ClassManager.ShouldBindMethod(m)));
123+
124+
if (result is null)
125+
throw new MissingMethodException($"Matching overload not found for {declaringType}.{name}");
126+
127+
return result;
128+
}
129+
130+
static bool MatchesGenericCount(MethodBase method, int genericCount)
131+
=> method.ContainsGenericParameters
132+
? method.GetGenericArguments().Length == genericCount
133+
: genericCount == 0;
134+
135+
static bool MatchesSignature(MethodBase method, ParameterHelper[] parameters)
136+
{
137+
var curr = method.GetParameters();
138+
if (curr.Length != parameters.Length) return false;
139+
for (int i = 0; i < curr.Length; i++)
140+
if (!parameters[i].Matches(curr[i])) return false;
141+
return true;
154142
}
155143

156144
public void GetObjectData(SerializationInfo serializationInfo, StreamingContext context)
@@ -159,11 +147,39 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext
159147
if (Valid)
160148
{
161149
serializationInfo.AddValue(SerializationMethodName, info.Name);
162-
serializationInfo.AddValue(SerializationType, info.ReflectedType.AssemblyQualifiedName);
150+
serializationInfo.AddValue(SerializationGenericParamCount,
151+
info.ContainsGenericParameters ? info.GetGenericArguments().Length : 0);
152+
serializationInfo.AddValue(SerializationFlags, (int)Flags(info));
153+
string? typeName = info.ReflectedType.AssemblyQualifiedName;
154+
Debug.Assert(typeName != null);
155+
serializationInfo.AddValue(SerializationType, typeName);
163156
ParameterHelper[] parameters = (from p in info.GetParameters() select new ParameterHelper(p)).ToArray();
164157
serializationInfo.AddValue(SerializationParameters, parameters, typeof(ParameterHelper[]));
165-
serializationInfo.AddValue(SerializationIsCtor, info.IsConstructor);
166158
}
167159
}
160+
161+
static MaybeMethodFlags Flags(MethodBase method)
162+
{
163+
var flags = MaybeMethodFlags.Default;
164+
if (method.IsConstructor) flags |= MaybeMethodFlags.Constructor;
165+
if (method.IsStatic) flags |= MaybeMethodFlags.Static;
166+
if (method.IsPublic) flags |= MaybeMethodFlags.Public;
167+
return flags;
168+
}
169+
170+
static MaybeMethodFlags Visibility(MethodBase method)
171+
=> Flags(method) & MaybeMethodFlags.Visibility;
172+
}
173+
174+
[Flags]
175+
internal enum MaybeMethodFlags
176+
{
177+
Default = 0,
178+
Constructor = 1,
179+
Static = 2,
180+
181+
// TODO: other kinds of visibility
182+
Public = 32,
183+
Visibility = Public,
168184
}
169185
}

src/runtime/runtime_data.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ private static void RestoreRuntimeDataObjects(SharedObjectsState storage)
247247
}
248248
}
249249

250-
private static IFormatter CreateFormatter()
250+
internal static IFormatter CreateFormatter()
251251
{
252252
return FormatterType != null ?
253253
(IFormatter)Activator.CreateInstance(FormatterType)

0 commit comments

Comments
 (0)
0