Skip to content

Commit e1770a2

Browse files
authored
Support __index__ on additional code paths in Bytes/ByteArray construction (#950)
* Use __index__ operator on more code paths * Test __index__ use in construction of bytes, bytearray * Remove support for BytesLikeAttribute on IBufferProtocol * Sanitize Bytes.FromObject
1 parent cfe008b commit e1770a2

6 files changed

Lines changed: 85 additions & 40 deletions

File tree

IronPythonAnalyzer/IronPythonAnalyzer/IronPythonAnalyzerAnalyzer.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class IronPythonAnalyzerAnalyzer : DiagnosticAnalyzer {
1919

2020
private static readonly DiagnosticDescriptor Rule1 = new DiagnosticDescriptor("IPY01", title: "Parameter which is marked not nullable does not have the NotNullAttribute", messageFormat: "Parameter '{0}' does not have the NotNullAttribute", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "Non-nullable reference type parameters should have the NotNullAttribute.");
2121
private static readonly DiagnosticDescriptor Rule2 = new DiagnosticDescriptor("IPY02", title: "Parameter which is marked nullable has the NotNullAttribute", messageFormat: "Parameter '{0}' should not have the NotNullAttribute", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "Nullable reference type parameters should not have the NotNullAttribute.");
22-
private static readonly DiagnosticDescriptor Rule3 = new DiagnosticDescriptor("IPY03", title: "BytesLikeAttribute used on a not supported type", messageFormat: "Parameter '{0}' declared bytes-like on unsupported type '{1}'", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "BytesLikeAttribute is only allowed on parameters of type ReadOnlyMemory<byte>, IReadOnlyList<byte>, or IList<byte>.");
22+
private static readonly DiagnosticDescriptor Rule3 = new DiagnosticDescriptor("IPY03", title: "BytesLikeAttribute used on a not supported type", messageFormat: "Parameter '{0}' declared bytes-like on unsupported type '{1}'", category: "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "BytesLikeAttribute is only allowed on parameters of type IReadOnlyList<byte>, or IList<byte>.");
2323

2424
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule1, Rule2, Rule3); } }
2525

@@ -57,11 +57,9 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) {
5757
var ilistType = context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IList`1");
5858
var ireadOnlyListOfByteType = ireadOnlyListType.Construct(byteType);
5959
var ilistOfByteType = ilistType.Construct(byteType);
60-
var ibufferProtocolType = context.Compilation.GetTypeByMetadataName("IronPython.Runtime.IBufferProtocol");
6160

6261
foreach (IParameterSymbol parameterSymbol in methodSymbol.Parameters) {
6362
if (parameterSymbol.GetAttributes().Any(x => x.AttributeClass.Equals(bytesLikeAttributeSymbol))
64-
&& !parameterSymbol.Type.Equals(ibufferProtocolType)
6563
&& !parameterSymbol.Type.Equals(ireadOnlyListOfByteType)
6664
&& !parameterSymbol.Type.Equals(ilistOfByteType)) {
6765
var diagnostic = Diagnostic.Create(Rule3, parameterSymbol.Locations[0], parameterSymbol.Name, parameterSymbol.Type.MetadataName);

Src/IronPython/Runtime/Binding/PythonOverloadResolver.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,22 +91,6 @@ public override Candidate SelectBestConversionFor(DynamicMetaObject arg, Paramet
9191
}
9292
}
9393

94-
bool isBytesLikeOne = IsBytesLikeParameter(candidateOne);
95-
bool isBytesLikeTwo = IsBytesLikeParameter(candidateTwo);
96-
97-
if (isBytesLikeOne) {
98-
if (isBytesLikeTwo) {
99-
return basePreferred;
100-
}
101-
if (candidateTwo.Type.IsInterface) {
102-
return Candidate.One;
103-
}
104-
} else if (isBytesLikeTwo) {
105-
if (candidateOne.Type.IsInterface) {
106-
return Candidate.Two;
107-
}
108-
}
109-
11094
return basePreferred;
11195
}
11296

Src/IronPython/Runtime/ByteArray.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,26 @@ public void __init__(int source) {
6868
}
6969
}
7070

71-
public void __init__([NotNull]IEnumerable<byte> source) {
72-
lock (this) {
73-
_bytes.Clear();
74-
_bytes.AddRange(source);
75-
}
76-
}
77-
78-
public void __init__([BytesLike, NotNull]IBufferProtocol source) {
79-
lock (this) {
80-
_bytes.Clear();
81-
using IPythonBuffer buffer = source.GetBuffer(BufferFlags.FullRO);
82-
_bytes.AddRange(buffer);
71+
public void __init__([NotNull]IBufferProtocol source) {
72+
if (Converter.TryConvertToIndex(source, throwOverflowError: true, out int size)) {
73+
__init__(size);
74+
} else {
75+
lock (this) {
76+
_bytes.Clear();
77+
using IPythonBuffer buffer = source.GetBuffer(BufferFlags.FullRO);
78+
_bytes.AddRange(buffer);
79+
}
8380
}
8481
}
8582

8683
public void __init__(CodeContext context, object? source) {
8784
if (Converter.TryConvertToIndex(source, throwOverflowError: true, out int size)) {
8885
__init__(size);
86+
} else if (source is IEnumerable<byte> en) {
87+
lock (this) {
88+
_bytes.Clear();
89+
_bytes.AddRange(en);
90+
}
8991
} else {
9092
lock (this) {
9193
_bytes.Clear();

Src/IronPython/Runtime/Bytes.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ public static object __new__(CodeContext context, [NotNull] PythonType cls, [Not
5959
return source;
6060
} else if (TryInvokeBytesOperator(context, source, out Bytes? res)) {
6161
return res;
62+
} else if (Converter.TryConvertToIndex(source, throwOverflowError: true, out int size)) {
63+
if (size < 0) throw PythonOps.ValueError("negative count");
64+
return new Bytes(new byte[size]);
6265
} else {
6366
return new Bytes(source);
6467
}
@@ -70,7 +73,16 @@ public static object __new__(CodeContext context, [NotNull] PythonType cls, [Not
7073
[StaticExtensionMethod]
7174
public static object __new__(CodeContext context, [NotNull] PythonType cls, object? @object) {
7275
if (cls == TypeCache.Bytes) {
73-
return FromObject(context, @object);
76+
if (@object?.GetType() == typeof(Bytes)) {
77+
return @object;
78+
} else if (TryInvokeBytesOperator(context, @object, out Bytes? res)) {
79+
return res;
80+
} else if (Converter.TryConvertToIndex(@object, throwOverflowError: true, out int size)) {
81+
if (size < 0) throw PythonOps.ValueError("negative count");
82+
return new Bytes(new byte[size]);
83+
} else {
84+
return new Bytes(ByteOps.GetBytes(@object, useHint: true, context).ToArray());
85+
}
7486
} else {
7587
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, @object));
7688
}
@@ -152,9 +164,6 @@ internal static Bytes FromObject(CodeContext context, object? o) {
152164
return (Bytes)o;
153165
} else if (TryInvokeBytesOperator(context, o, out Bytes? res)) {
154166
return res;
155-
} else if (Converter.TryConvertToIndex(o, throwOverflowError: true, out int size)) {
156-
if (size < 0) throw PythonOps.ValueError("negative count");
157-
return new Bytes(new byte[size]);
158167
} else {
159168
return new Bytes(ByteOps.GetBytes(o, useHint: true, context).ToArray());
160169
}

Src/IronPython/Runtime/BytesLikeAttribute.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@
88

99
namespace IronPython.Runtime {
1010
/// <summary>
11-
/// For <c>IList〈byte〉</c>, <c>IReadOnlyList〈byte〉</c>, and <c>IBufferProtocol</c> parameters:
11+
/// For <c>IList〈byte〉</c> and <c>IReadOnlyList〈byte〉</c> parameters:
1212
/// Marks that the parameter is typed to accept a bytes-like object.
1313
/// <br/>
14-
/// If applied on a IList〈byte〉 parameter, this attribute disallows passing
14+
/// It also disallows passing
1515
/// a Python list object and auto-applying our generic conversion.
16-
/// <br/>
17-
/// The overload resolver will favor an overload with a BytesLike parameter
18-
/// over an otherwise equivalent overload with a different interface parameter.
1916
/// </summary>
2017
/// <remarks>
2118
/// A bytes-like object is any object of type implementing IBufferProtocol.

Tests/test_bytes.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,15 @@ def __bytes__(self):
203203
self.assertIs(type(bytes(SomeClass())), OtherBytesSubclass)
204204
self.assertEqual(BytesSubclass(SomeClass()), b'SOME CLASS')
205205
self.assertIs(type(BytesSubclass(SomeClass())), BytesSubclass)
206+
self.assertEqual(int.from_bytes(bytes(SomeClass()), 'big'), int.from_bytes(b"SOME CLASS", 'big'))
206207

207208
class BytesBytesSubclass(bytes):
208209
def __bytes__(self):
209210
return BytesBytesSubclass(b"BYTES FROM BYTES")
210211

211212
self.assertEqual(bytes(BytesBytesSubclass(b"JUST BYTES")), b"BYTES FROM BYTES")
212213
self.assertIs(type(bytes(BytesBytesSubclass(b"JUST BYTES"))), BytesBytesSubclass)
214+
self.assertEqual(int.from_bytes(bytes(BytesBytesSubclass(b"JUST BYTES")), 'big'), int.from_bytes(b"BYTES FROM BYTES", 'big'))
213215

214216
class ListSubclass(bytes):
215217
def __bytes__(self):
@@ -243,6 +245,59 @@ def __bytes__(self):
243245
self.assertIs(type(bytes(IntSubclass(-1))), OtherBytesSubclass)
244246
self.assertEqual(BytesSubclass(IntSubclass(-1)), b"BYTES FROM INT")
245247
self.assertIs(type(BytesSubclass(IntSubclass(-1))), BytesSubclass)
248+
self.assertEqual(int.from_bytes(IntSubclass(555), 'big'), int.from_bytes(b"BYTES FROM INT", 'big'))
249+
250+
def test_dunder_index(self):
251+
class IndexableBytes(bytes):
252+
def __init__(self, value):
253+
self.value = len(value)
254+
def __index__(self):
255+
return self.value
256+
257+
class IndexableBytearray(bytearray):
258+
def __init__(self, value):
259+
super().__init__(value)
260+
self.value = len(value)
261+
def __index__(self):
262+
return self.value
263+
264+
class IndexableStr(str):
265+
def __init__(self, value):
266+
self.value = len(value)
267+
def __index__(self):
268+
return self.value
269+
270+
class IndexableInt(int):
271+
def __init__(self, value):
272+
self.value = value
273+
def __index__(self):
274+
return self.value + 10
275+
276+
ib = IndexableBytes(b"xyz")
277+
iba = IndexableBytearray(b"abcd")
278+
istr = IndexableStr("abcde")
279+
ii = IndexableInt(2)
280+
self.assertEqual(ii.__index__(), 12)
281+
282+
self.assertEqual(ib, b"xyz")
283+
self.assertEqual(iba, bytearray(b"abcd"))
284+
self.assertEqual(istr, "abcde")
285+
286+
self.assertEqual(bytes(ib), bytes(3))
287+
self.assertEqual(bytes(iba), bytes(4))
288+
self.assertRaises(TypeError, bytes, istr)
289+
self.assertEqual(bytes(ii), bytes(2))
290+
291+
self.assertEqual(bytearray(ib), bytearray(3))
292+
self.assertEqual(bytearray(iba), bytearray(4))
293+
self.assertRaises(TypeError, bytearray, istr)
294+
self.assertEqual(bytearray(ii), bytes(2))
295+
296+
self.assertEqual(int.from_bytes(IndexableBytes(b"abc"), 'big'), 0x616263)
297+
self.assertEqual(int.from_bytes(IndexableBytearray(b"abc"), 'big'), 0x616263)
298+
self.assertRaises(TypeError, int.from_bytes, IndexableStr("abc"), 'big')
299+
self.assertRaises(TypeError, int.from_bytes, IndexableInt(2), 'big')
300+
self.assertRaises(TypeError, int.from_bytes, 2, 'big')
246301

247302
def test_capitalize(self):
248303
tests = [(b'foo', b'Foo'),

0 commit comments

Comments
 (0)