| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -103,6 +103,7 @@ Instead, `PyIterable` does that. | |||
| 103 | 103 | - Providing an invalid type parameter to a generic type or method produces a helpful Python error | |
| 104 | 104 | - Empty parameter names (as can be generated from F#) do not cause crashes | |
| 105 | 105 | - 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) | ||
| 106 | 107 | ||
| 107 | 108 | ### Removed | |
| 108 | 109 | ||
| Original file line number | Diff line number | Diff 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, 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 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,18 +1,20 @@ | |||
| 1 | 1 | using System; | |
| 2 | 2 | using System.Collections.Generic; | |
| 3 | + using System.Diagnostics; | ||
| 4 | + using System.Linq; | ||
| 3 | 5 | using System.Reflection; | |
| 4 | 6 | ||
| 5 | 7 | namespace Python.Runtime.Reflection; | |
| 6 | 8 | ||
| 7 | 9 | [Serializable] | |
| 8 | - struct ParameterHelper : IEquatable<ParameterInfo> | ||
| 10 | + class ParameterHelper : IEquatable<ParameterInfo> | ||
| 9 | 11 | { | |
| 10 | 12 | public readonly string TypeName; | |
| 11 | 13 | public readonly ParameterModifier Modifier; | |
| 14 | + public readonly ParameterHelper[]? GenericArguments; | ||
| 12 | 15 | ||
| 13 | - public ParameterHelper(ParameterInfo tp) | ||
| 16 | + public ParameterHelper(ParameterInfo tp) : this(tp.ParameterType) | ||
| 14 | 17 | { | |
| 15 | - TypeName = tp.ParameterType.AssemblyQualifiedName; | ||
| 16 | 18 | Modifier = ParameterModifier.None; | |
| 17 | 19 | ||
| 18 | 20 | if (tp.IsIn && tp.ParameterType.IsByRef) | |
@@ -29,12 +31,55 @@ public ParameterHelper(ParameterInfo tp) | |||
| 29 | 31 | } | |
| 30 | 32 | } | |
| 31 | 33 | ||
| 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 | + | ||
| 32 | 55 | public bool Equals(ParameterInfo other) | |
| 33 | 56 | { | |
| 34 | 57 | return this.Equals(new ParameterHelper(other)); | |
| 35 | 58 | } | |
| 36 | 59 | ||
| 37 | 60 | 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 | + } | ||
| 38 | 83 | } | |
| 39 | 84 | ||
| 40 | 85 | enum ParameterModifier | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,5 @@ | |||
| 1 | 1 | using System; | |
| 2 | + using System.Diagnostics; | ||
| 2 | 3 | using System.Diagnostics.CodeAnalysis; | |
| 3 | 4 | using System.Reflection; | |
| 4 | 5 | using System.Runtime.Serialization; | |
@@ -17,8 +18,9 @@ internal struct MaybeMethodBase<T> : ISerializable where T: MethodBase | |||
| 17 | 18 | const string SerializationType = "t"; | |
| 18 | 19 | // Fhe parameters of the MethodBase | |
| 19 | 20 | const string SerializationParameters = "p"; | |
| 20 | - const string SerializationIsCtor = "c"; | ||
| 21 | 21 | const string SerializationMethodName = "n"; | |
| 22 | + const string SerializationGenericParamCount = "G"; | ||
| 23 | + const string SerializationFlags = "V"; | ||
| 22 | 24 | ||
| 23 | 25 | public static implicit operator MaybeMethodBase<T> (T? ob) => new (ob); | |
| 24 | 26 | ||
@@ -62,6 +64,7 @@ public MaybeMethodBase(T? mi) | |||
| 62 | 64 | { | |
| 63 | 65 | info = mi; | |
| 64 | 66 | name = mi?.ToString(); | |
| 67 | + Debug.Assert(name != null || info == null); | ||
| 65 | 68 | deserializationException = null; | |
| 66 | 69 | } | |
| 67 | 70 | ||
@@ -82,75 +85,60 @@ internal MaybeMethodBase(SerializationInfo serializationInfo, StreamingContext c | |||
| 82 | 85 | { | |
| 83 | 86 | throw new SerializationException($"The underlying type {typeName} can't be found"); | |
| 84 | 87 | } | |
| 88 | + | ||
| 89 | + var flags = (MaybeMethodFlags)serializationInfo.GetInt32(SerializationFlags); | ||
| 90 | + int genericCount = serializationInfo.GetInt32(SerializationGenericParamCount); | ||
| 91 | + | ||
| 85 | 92 | // Get the method's parameters types | |
| 86 | 93 | var field_name = serializationInfo.GetString(SerializationMethodName); | |
| 87 | 94 | 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 | - } | ||
| 103 | 95 | ||
| 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.BindingFlags, 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); | ||
| 125 | 97 | } | |
| 126 | 98 | catch (Exception e) | |
| 127 | 99 | { | |
| 128 | 100 | deserializationException = e; | |
| 129 | 101 | } | |
| 130 | 102 | } | |
| 131 | 103 | ||
| 132 | - MethodBase? CheckRefTypes(MethodBase mb, ParameterHelper[] ph) | ||
| 104 | + static MethodBase ScanForMethod(Type declaringType, string name, int genericCount, MaybeMethodFlags flags, ParameterHelper[] parameters) | ||
| 133 | 105 | { | |
| 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; | ||
| 152 | 108 | ||
| 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; | ||
| 154 | 142 | } | |
| 155 | 143 | ||
| 156 | 144 | public void GetObjectData(SerializationInfo serializationInfo, StreamingContext context) | |
@@ -159,11 +147,39 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext | |||
| 159 | 147 | if (Valid) | |
| 160 | 148 | { | |
| 161 | 149 | 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); | ||
| 163 | 156 | ParameterHelper[] parameters = (from p in info.GetParameters() select new ParameterHelper(p)).ToArray(); | |
| 164 | 157 | serializationInfo.AddValue(SerializationParameters, parameters, typeof(ParameterHelper[])); | |
| 165 | - serializationInfo.AddValue(SerializationIsCtor, info.IsConstructor); | ||
| 166 | 158 | } | |
| 167 | 159 | } | |
| 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, | ||
| 168 | 184 | } | |
| 169 | 185 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -247,7 +247,7 @@ private static void RestoreRuntimeDataObjects(SharedObjectsState storage) | |||
| 247 | 247 | } | |
| 248 | 248 | } | |
| 249 | 249 | ||
| 250 | - private static IFormatter CreateFormatter() | ||
| 250 | + internal static IFormatter CreateFormatter() | ||
| 251 | 251 | { | |
| 252 | 252 | return FormatterType != null ? | |
| 253 | 253 | (IFormatter)Activator.CreateInstance(FormatterType) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments