Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class IronPythonAnalyzerAnalyzer : DiagnosticAnalyzer {

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.");
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.");
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>.");
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>.");

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

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

foreach (IParameterSymbol parameterSymbol in methodSymbol.Parameters) {
if (parameterSymbol.GetAttributes().Any(x => x.AttributeClass.Equals(bytesLikeAttributeSymbol))
&& !parameterSymbol.Type.Equals(ibufferProtocolType)
&& !parameterSymbol.Type.Equals(ireadOnlyListOfByteType)
&& !parameterSymbol.Type.Equals(ilistOfByteType)) {
var diagnostic = Diagnostic.Create(Rule3, parameterSymbol.Locations[0], parameterSymbol.Name, parameterSymbol.Type.MetadataName);
Expand Down
16 changes: 0 additions & 16 deletions Src/IronPython/Runtime/Binding/PythonOverloadResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,6 @@ public override Candidate SelectBestConversionFor(DynamicMetaObject arg, Paramet
}
}

bool isBytesLikeOne = IsBytesLikeParameter(candidateOne);
bool isBytesLikeTwo = IsBytesLikeParameter(candidateTwo);

if (isBytesLikeOne) {
if (isBytesLikeTwo) {
return basePreferred;
}
if (candidateTwo.Type.IsInterface) {
return Candidate.One;
}
} else if (isBytesLikeTwo) {
if (candidateOne.Type.IsInterface) {
return Candidate.Two;
}
}

return basePreferred;
}

Expand Down
26 changes: 14 additions & 12 deletions Src/IronPython/Runtime/ByteArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,26 @@ public void __init__(int source) {
}
}

public void __init__([NotNull]IEnumerable<byte> source) {
lock (this) {
_bytes.Clear();
_bytes.AddRange(source);
}
}

public void __init__([BytesLike, NotNull]IBufferProtocol source) {
lock (this) {
_bytes.Clear();
using IPythonBuffer buffer = source.GetBuffer(BufferFlags.FullRO);
_bytes.AddRange(buffer);
public void __init__([NotNull]IBufferProtocol source) {
if (Converter.TryConvertToIndex(source, throwOverflowError: true, out int size)) {
__init__(size);
} else {
lock (this) {
_bytes.Clear();
using IPythonBuffer buffer = source.GetBuffer(BufferFlags.FullRO);
_bytes.AddRange(buffer);
}
}
}

public void __init__(CodeContext context, object? source) {
if (Converter.TryConvertToIndex(source, throwOverflowError: true, out int size)) {
__init__(size);
} else if (source is IEnumerable<byte> en) {
lock (this) {
_bytes.Clear();
_bytes.AddRange(en);
}
} else {
lock (this) {
_bytes.Clear();
Expand Down
17 changes: 13 additions & 4 deletions Src/IronPython/Runtime/Bytes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public static object __new__(CodeContext context, [NotNull] PythonType cls, [Not
return source;
} else if (TryInvokeBytesOperator(context, source, out Bytes? res)) {
return res;
} else if (Converter.TryConvertToIndex(source, throwOverflowError: true, out int size)) {
if (size < 0) throw PythonOps.ValueError("negative count");
return new Bytes(new byte[size]);
} else {
return new Bytes(source);
}
Expand All @@ -70,7 +73,16 @@ public static object __new__(CodeContext context, [NotNull] PythonType cls, [Not
[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, object? @object) {
if (cls == TypeCache.Bytes) {
return FromObject(context, @object);
if (@object?.GetType() == typeof(Bytes)) {
return @object;
} else if (TryInvokeBytesOperator(context, @object, out Bytes? res)) {
return res;
} else if (Converter.TryConvertToIndex(@object, throwOverflowError: true, out int size)) {
if (size < 0) throw PythonOps.ValueError("negative count");
return new Bytes(new byte[size]);
} else {
return new Bytes(ByteOps.GetBytes(@object, useHint: true, context).ToArray());
}
} else {
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, @object));
}
Expand Down Expand Up @@ -152,9 +164,6 @@ internal static Bytes FromObject(CodeContext context, object? o) {
return (Bytes)o;
} else if (TryInvokeBytesOperator(context, o, out Bytes? res)) {
return res;
} else if (Converter.TryConvertToIndex(o, throwOverflowError: true, out int size)) {
if (size < 0) throw PythonOps.ValueError("negative count");
return new Bytes(new byte[size]);
} else {
return new Bytes(ByteOps.GetBytes(o, useHint: true, context).ToArray());
}
Expand Down
7 changes: 2 additions & 5 deletions Src/IronPython/Runtime/BytesLikeAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@

namespace IronPython.Runtime {
/// <summary>
/// For <c>IList〈byte〉</c>, <c>IReadOnlyList〈byte〉</c>, and <c>IBufferProtocol</c> parameters:
/// For <c>IList〈byte〉</c> and <c>IReadOnlyList〈byte〉</c> parameters:
/// Marks that the parameter is typed to accept a bytes-like object.
/// <br/>
/// If applied on a IList〈byte〉 parameter, this attribute disallows passing
/// It also disallows passing
/// a Python list object and auto-applying our generic conversion.
/// <br/>
/// The overload resolver will favor an overload with a BytesLike parameter
/// over an otherwise equivalent overload with a different interface parameter.
/// </summary>
/// <remarks>
/// A bytes-like object is any object of type implementing IBufferProtocol.
Expand Down
55 changes: 55 additions & 0 deletions Tests/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,15 @@ def __bytes__(self):
self.assertIs(type(bytes(SomeClass())), OtherBytesSubclass)
self.assertEqual(BytesSubclass(SomeClass()), b'SOME CLASS')
self.assertIs(type(BytesSubclass(SomeClass())), BytesSubclass)
self.assertEqual(int.from_bytes(bytes(SomeClass()), 'big'), int.from_bytes(b"SOME CLASS", 'big'))

class BytesBytesSubclass(bytes):
def __bytes__(self):
return BytesBytesSubclass(b"BYTES FROM BYTES")

self.assertEqual(bytes(BytesBytesSubclass(b"JUST BYTES")), b"BYTES FROM BYTES")
self.assertIs(type(bytes(BytesBytesSubclass(b"JUST BYTES"))), BytesBytesSubclass)
self.assertEqual(int.from_bytes(bytes(BytesBytesSubclass(b"JUST BYTES")), 'big'), int.from_bytes(b"BYTES FROM BYTES", 'big'))

class ListSubclass(bytes):
def __bytes__(self):
Expand Down Expand Up @@ -243,6 +245,59 @@ def __bytes__(self):
self.assertIs(type(bytes(IntSubclass(-1))), OtherBytesSubclass)
self.assertEqual(BytesSubclass(IntSubclass(-1)), b"BYTES FROM INT")
self.assertIs(type(BytesSubclass(IntSubclass(-1))), BytesSubclass)
self.assertEqual(int.from_bytes(IntSubclass(555), 'big'), int.from_bytes(b"BYTES FROM INT", 'big'))

def test_dunder_index(self):
class IndexableBytes(bytes):
def __init__(self, value):
self.value = len(value)
def __index__(self):
return self.value

class IndexableBytearray(bytearray):
def __init__(self, value):
super().__init__(value)
self.value = len(value)
def __index__(self):
return self.value

class IndexableStr(str):
def __init__(self, value):
self.value = len(value)
def __index__(self):
return self.value

class IndexableInt(int):
def __init__(self, value):
self.value = value
def __index__(self):
return self.value + 10

ib = IndexableBytes(b"xyz")
iba = IndexableBytearray(b"abcd")
istr = IndexableStr("abcde")
ii = IndexableInt(2)
self.assertEqual(ii.__index__(), 12)

self.assertEqual(ib, b"xyz")
self.assertEqual(iba, bytearray(b"abcd"))
self.assertEqual(istr, "abcde")

self.assertEqual(bytes(ib), bytes(3))
self.assertEqual(bytes(iba), bytes(4))
self.assertRaises(TypeError, bytes, istr)
self.assertEqual(bytes(ii), bytes(2))

self.assertEqual(bytearray(ib), bytearray(3))
self.assertEqual(bytearray(iba), bytearray(4))
self.assertRaises(TypeError, bytearray, istr)
self.assertEqual(bytearray(ii), bytes(2))

self.assertEqual(int.from_bytes(IndexableBytes(b"abc"), 'big'), 0x616263)
self.assertEqual(int.from_bytes(IndexableBytearray(b"abc"), 'big'), 0x616263)
self.assertRaises(TypeError, int.from_bytes, IndexableStr("abc"), 'big')
self.assertRaises(TypeError, int.from_bytes, IndexableInt(2), 'big')
self.assertRaises(TypeError, int.from_bytes, 2, 'big')

def test_capitalize(self):
tests = [(b'foo', b'Foo'),
Expand Down