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
49 changes: 7 additions & 42 deletions Src/IronPython/Runtime/ByteArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public void __init__(CodeContext context, object? source) {
}
IEnumerator ie = PythonOps.GetEnumerator(context, source);
while (ie.MoveNext()) {
Add(GetByte(ie.Current));
Add(ByteOps.GetByte(ie.Current));
}
}
}
Expand Down Expand Up @@ -127,7 +127,7 @@ public void append(int item) {

public void append(object? item) {
lock (this) {
_bytes.Add(GetByte(item));
_bytes.Add(ByteOps.GetByte(item));
}
}

Expand All @@ -138,13 +138,13 @@ public void extend([NotNull]IEnumerable<byte> seq) {
}
}

public void extend(object? seq) {
public void extend(CodeContext context, object? seq) {
// We don't make use of the length hint when extending the byte array.
// However, in order to match CPython behavior with invalid length hints we
// we need to go through the motions and get the length hint and attempt
// to convert it to an int.

extend(GetBytes(seq, useHint: true));
extend(ByteOps.GetBytes(seq, useHint: true, context));
}

public void insert(int index, int value) {
Expand Down Expand Up @@ -205,7 +205,7 @@ public void remove(int value) {

public void remove(object? value) {
lock (this) {
RemoveByte(GetByte(value));
RemoveByte(ByteOps.GetByte(value));
}
}

Expand Down Expand Up @@ -1214,7 +1214,7 @@ public object? this[int index] {
}
set {
lock (this) {
_bytes[PythonOps.FixIndex(index, _bytes.Count)] = GetByte(value);
_bytes[PythonOps.FixIndex(index, _bytes.Count)] = ByteOps.GetByte(value);
}
}
}
Expand Down Expand Up @@ -1251,7 +1251,7 @@ public object? this[[NotNull]Slice slice] {
// integers, longs, etc... - fill in an array of 0 bytes
// list of bytes, indexables, etc...

IList<byte> list = GetBytes(value, useHint: false);
IList<byte> list = ByteOps.GetBytes(value, useHint: false);

lock (this) {
slice.indices(_bytes.Count, out int start, out int stop, out int step);
Expand Down Expand Up @@ -1345,41 +1345,6 @@ private void SliceNoStep(int start, int stop, IList<byte> other) {
}
}

private static byte GetByte(object? value) {
if (Converter.TryConvertToIndex(value, out object index)) {
switch (index) {
case int i: return i.ToByteChecked();
case BigInteger bi: return bi.ToByteChecked();
default: throw new InvalidOperationException(); // unreachable
}
}
throw PythonOps.TypeError("an integer is required");
}

internal static IList<byte> GetBytes(object? value, bool useHint) {
switch (value) {
case IList<byte> lob when !(lob is ListGenericWrapper<byte>):
return lob;
case IBufferProtocol bp:
using (IPythonBuffer buf = bp.GetBuffer()) {
return buf.AsReadOnlySpan().ToArray();
}
case ReadOnlyMemory<byte> rom:
return rom.ToArray();
case Memory<byte> mem:
return mem.ToArray();
default:
int len = 0;
if (useHint) PythonOps.TryInvokeLengthHint(DefaultContext.Default, value, out len);
List<byte> ret = new List<byte>(len);
IEnumerator ie = PythonOps.GetEnumerator(value);
while (ie.MoveNext()) {
ret.Add(GetByte(ie.Current));
}
return ret;
}
}

#endregion

#region IList<byte> Members
Expand Down
137 changes: 111 additions & 26 deletions Src/IronPython/Runtime/Bytes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@
using System.Linq;
using System.Linq.Expressions;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;

using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;

using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;
using IronPython.Hosting;
using NotNullWhenAttribute = System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;

namespace IronPython.Runtime {
[PythonType("bytes"), Serializable]
Expand All @@ -31,53 +30,110 @@ public Bytes() {
_bytes = new byte[0];
}

public Bytes([NotNull]IEnumerable<byte> bytes) {
public Bytes([NotNull] Bytes bytes) {
_bytes = bytes._bytes;
}

public Bytes([NotNull] IEnumerable<byte> bytes) {
_bytes = bytes.ToArray();
}

public Bytes([BytesLike, NotNull]IBufferProtocol source) {
public Bytes([NotNull] IBufferProtocol source) {
using IPythonBuffer buffer = source.GetBuffer(BufferFlags.FullRO);
_bytes = buffer.ToArray();
}

public Bytes(CodeContext context, object? source) {
if (PythonTypeOps.TryInvokeUnaryOperator(context, source, "__bytes__", out object? res)) {
if (res is Bytes bytes) {
_bytes = bytes._bytes;
[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls) {
if (cls == TypeCache.Bytes) {
return Empty;
} else {
return cls.CreateInstance(context);
}
}

[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, [NotNull] IBufferProtocol source) {
if (cls == TypeCache.Bytes) {
if (source.GetType() == typeof(Bytes)) {
return source;
} else if (TryInvokeBytesOperator(context, source, out Bytes? res)) {
return res;
} else {
throw PythonOps.TypeError("__bytes__ returned non-bytes (got '{0}' from type '{1}')", PythonOps.GetPythonTypeName(res), PythonOps.GetPythonTypeName(source));
return new Bytes(source);
}
} else if (Converter.TryConvertToIndex(source, throwOverflowError: true, out int size)) {
if (size < 0) throw PythonOps.ValueError("negative count");
_bytes = new byte[size];
} else {
_bytes = ByteArray.GetBytes(source, useHint: true).ToArray();
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, source));
}
}

public Bytes([NotNull]IEnumerable<object?> source) {
_bytes = source.Select(b => ((int)PythonOps.Index(b)).ToByteChecked()).ToArray();
[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, object? @object) {
if (cls == TypeCache.Bytes) {
return FromObject(context, @object);
} else {
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, @object));
}
}

public Bytes([NotNull]PythonList bytes) {
_bytes = ByteOps.GetBytes(bytes).ToArray();
[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, [NotNull] Extensible<int> size) {
if (cls == TypeCache.Bytes) {
if (TryInvokeBytesOperator(context, size, out Bytes? res)) {
return res;
} else {
if (size < 0) throw PythonOps.ValueError("negative count");
return new Bytes(new byte[size]);
}
} else {
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, size));
}
}

public Bytes(int size) {
if (size < 0) throw PythonOps.ValueError("negative count");
_bytes = new byte[size];
[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, int size) {
if (cls == TypeCache.Bytes) {
if (size < 0) throw PythonOps.ValueError("negative count");
return new Bytes(new byte[size]);
} else {
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, size));
}
}

public Bytes([NotNull]string @string) {
[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, [NotNull] ExtensibleString @string) {
if (cls == TypeCache.Bytes) {
if (TryInvokeBytesOperator(context, @string, out Bytes? res)) {
return res;
} else {
throw PythonOps.TypeError("string argument without an encoding");
}
} else {
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, @string));
}
}

[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, [NotNull] string @string) {
throw PythonOps.TypeError("string argument without an encoding");
}

public Bytes(CodeContext context, [NotNull]string @string, [NotNull]string encoding) {
_bytes = StringOps.encode(context, @string, encoding, "strict").UnsafeByteArray;
[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, [NotNull] string @string, [NotNull] string encoding) {
if (cls == TypeCache.Bytes) {
return StringOps.encode(context, @string, encoding);
} else {
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, @string, encoding));
}
}

public Bytes(CodeContext context, [NotNull]string @string, [NotNull]string encoding, [NotNull]string errors) {
_bytes = StringOps.encode(context, @string, encoding, errors).UnsafeByteArray;
[StaticExtensionMethod]
public static object __new__(CodeContext context, [NotNull] PythonType cls, [NotNull] string @string, [NotNull] string encoding, [NotNull] string errors) {
if (cls == TypeCache.Bytes) {
return StringOps.encode(context, @string, encoding, errors);
} else {
return cls.CreateInstance(context, __new__(context, TypeCache.Bytes, @string, encoding, errors));
}
}

private Bytes(byte[] bytes) {
Expand All @@ -89,6 +145,21 @@ private Bytes(byte[] bytes) {
internal static Bytes FromByte(byte b)
=> oneByteBytes[b];

internal static Bytes FromObject(CodeContext context, object? o) {
if (o == null) {
throw PythonOps.TypeError("cannot convert 'NoneType' object to bytes");
} else if (o.GetType() == typeof(Bytes)) {
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());
}
}

internal static Bytes Make(byte[] bytes)
=> new Bytes(bytes);

Expand Down Expand Up @@ -364,7 +435,7 @@ public Bytes join(object? sequence) {

public Bytes join([NotNull]PythonList sequence) {
if (sequence.__len__() == 0) {
return new Bytes();
return Empty;
} else if (sequence.__len__() == 1) {
return JoinOne(sequence[0]);
}
Expand Down Expand Up @@ -892,6 +963,20 @@ internal ReadOnlyMemory<byte> AsMemory() {
return _bytes.AsMemory();
}

private static bool TryInvokeBytesOperator(CodeContext context, object? obj, [NotNullWhen(true)] out Bytes? bytes) {
if (PythonTypeOps.TryInvokeUnaryOperator(context, obj, "__bytes__", out object? res)) {
if (res is Bytes b) {
bytes = b;
return true;
} else {
throw PythonOps.TypeError("__bytes__ returned non-bytes (got '{0}' from type '{1}')", PythonOps.GetPythonTypeName(res), PythonOps.GetPythonTypeName(obj));
}
} else {
bytes = null;
return false;
}
}

private static Bytes JoinOne(object? curVal) {
if (curVal?.GetType() == typeof(Bytes)) {
return (Bytes)curVal;
Expand Down
35 changes: 30 additions & 5 deletions Src/IronPython/Runtime/Operations/ByteOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,31 @@ internal static IList<byte> CoerceBytes(object? obj) {
throw PythonOps.TypeError("a bytes-like object is required, not '{0}'", PythonTypeOps.GetName(obj));
}

internal static List<byte> GetBytes(ICollection bytes) {
return bytes.Select(GetByte).ToList();
internal static IList<byte> GetBytes(object? value, bool useHint, CodeContext? context = null) {
switch (value) {
case IList<byte> lob when !(lob is ListGenericWrapper<byte>):
return lob;
case IBufferProtocol bp:
using (IPythonBuffer buf = bp.GetBuffer()) {
return buf.AsReadOnlySpan().ToArray();
}
case ReadOnlyMemory<byte> rom:
return rom.ToArray();
case Memory<byte> mem:
return mem.ToArray();
default:
int len = 0;
if (useHint) PythonOps.TryInvokeLengthHint(context ?? DefaultContext.Default, value, out len);
List<byte> ret = new List<byte>(len);
IEnumerator ie = PythonOps.GetEnumerator(value);
while (ie.MoveNext()) {
ret.Add(GetByte(ie.Current));
}
return ret;
}
}

private static byte GetByte(object? o) {
internal static byte GetByte(object? o) {
// TODO: move fast paths to TryConvertToIndex?
switch (o) {
case int ii:
Expand All @@ -135,8 +155,13 @@ private static byte GetByte(object? o) {
return ((BigInteger)ui).ToByteChecked();
}

if (Converter.TryConvertToIndex(o, out int i))
return i.ToByteChecked();
if (Converter.TryConvertToIndex(o, out object index)) {
switch (index) {
case int i: return i.ToByteChecked();
case BigInteger bi: return bi.ToByteChecked();
default: throw new InvalidOperationException(); // unreachable
}
}

throw PythonOps.TypeError($"'{PythonTypeOps.GetName(o)}' object cannot be interpreted as an integer");
}
Expand Down
2 changes: 1 addition & 1 deletion Src/IronPython/Runtime/Operations/IntOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ public static BigInteger from_bytes(CodeContext context, object bytes, string by
bool isLittle = byteorder == "little";
if (!isLittle && byteorder != "big") throw PythonOps.ValueError("byteorder must be either 'little' or 'big'");

return FromBytes(new Bytes(context, bytes), isLittle, signed);
return FromBytes(Bytes.FromObject(context, bytes), isLittle, signed);
}

private static BigInteger FromBytes(IList<byte> bytes, bool isLittle, bool signed) {
Expand Down
8 changes: 8 additions & 0 deletions Src/IronPython/Runtime/Types/TypeCache.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static class TypeCache {
private static PythonType setcollection;
private static PythonType pythontype;
private static PythonType str;
private static PythonType bytes;
private static PythonType pythontuple;
private static PythonType weakreference;
private static PythonType pythonlist;
Expand Down Expand Up @@ -123,6 +124,13 @@ public static PythonType String {
}
}

public static PythonType Bytes {
get {
if (bytes == null) bytes = DynamicHelpers.GetPythonTypeFromType(typeof(Bytes));
return bytes;
}
}

public static PythonType PythonTuple {
get {
if (pythontuple == null) pythontuple = DynamicHelpers.GetPythonTypeFromType(typeof(PythonTuple));
Expand Down
1 change: 1 addition & 0 deletions Src/Scripts/generate_typecache.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def __init__(self, type, name=None, typeType='PythonType', entryName=None):
TypeData('SetCollection', entryName='Set'),
TypeData('PythonType'),
TypeData('String', 'str'),
TypeData('Bytes'),
TypeData('PythonTuple'),
TypeData('WeakReference'),
TypeData('PythonList'),
Expand Down
Loading