tiny.python.object.value
Defined in object.
Every Python value the package handles is one tagged union: None, booleans and integers are held in the value itself, a string is a slice of bytes held elsewhere, and lists, tuples, dictionaries, ranges and the other objects are pointers to objects held elsewhere.
API (25)
Actions
Public operations.
Dict.clearRetainingCapacity: Removes every entry and keeps the memory of both the entries and the hash table for reuse.Dict.deinit: Frees the entries and the hash table with the given allocator and leaves the dictionary undefined.Dict.ensureTotalCapacity: Reserves room for at leastcapacityentries in both the entry array and the hash table.Dict.indexOf: Returns the position of the key's entry inentries, or null when the key is absent.Dict.popLast: Removes the most recently inserted entry and returns it, or returns null when the dictionary is empty.Dict.put: When an equal key is present, replaces that entry's value, and the entry keeps its position and its first key.Dict.removeAt: Removes the entry at the given position and returns its value.
Types and contracts
Public types and contracts.
Builtin: The builtin functions that the package provides, one tag each, named after the function:dict,enumerate,iter,len,list,next,range,reversedandtuple.Dict: A Python dictionary that keeps insertion order: an array of key and value entries, in the order the keys first arrived, and a hash table from each key to its entry's position.DictEntry: One key and its value in a dictionary.DictIterator: The state of a walk over a dictionary's keys, in insertion order or from the newest key back, forcreateDictIteratorandcreateDictReverseIterator.DictView: A live view of one dictionary.DictViewIterator: The state of a walk over a dictionary view, forcreateDictViewIteratorandcreateDictViewReverseIterator: its keys, its values, or its entries as key and value tuples.DictViewKind: The part of a dictionary a view shows, one tag each, named after the Python method that returns the view:keys,valuesanditems.EnumerateIterator: The state of anenumeratewalk forcreateEnumerateIterator: an inner iterator and the counter value its next item gets.Iterator: The state of one walk forforloops,iter,next,enumerate,reversed, and the consuming builtins, as one tag per kind of source: a dictionary's keys,enumerate, a dictionary view, a list, a tuple, a range, or a string.List: A Python list, as a Zig growable array of values.ListIterator: The state of a walk over a list, from the first item or from the last, forcreateListIteratorandcreateListReverseIterator.NativeMethod: A list or dictionary method bound to the object it was read from, as a tagged union with one tag per method.Range: A Python range: the integers fromstarttowardstopin steps ofstep, with the count of elements stored.RangeIterator: The state of a walk over a range's elements, from its start or from its last element, forcreateRangeIteratorandcreateRangeReverseIterator.StringIterator: The state of a walk over a string's codepoints, from the first or from the last, forcreateStringIteratorandcreateStringReverseIterator.Tuple: A Python tuple, as a fixed slice of values.TupleIterator: The state of a walk over a tuple, from the first item or from the last, forcreateTupleIteratorandcreateTupleReverseIterator.Value: One Python value, as a tagged union of thirteen kinds.
Source
Source: lib/python/src/object/root.zig:10
zig
pub const value = @import("value.zig");Source: lib/python/src/object/value.zig
zig
//! Every Python value the package handles is one tagged union: `None`, booleans and integers are//! held in the value itself, a string is a slice of bytes held elsewhere, and lists, tuples,//! dictionaries, ranges and the other objects are pointers to objects held elsewhere.//!//! The virtual machine copies values between its stack, its variables and its containers at every//! step, so a value has to be small and cheap to copy. Python's rules for truth, equality and//! hashing have to hold across all of the types.//!//! Python treats `True` and `False` as the integers 1 and 0 in arithmetic, equality and hashing, so//! `True == 1` holds and a dictionary finds the key `1` under `True`. A dictionary has to keep//! insertion order and find keys by hash, and only values that cannot change may be keys. Two lists//! can hold the same items and still be different objects, so equality and identity are separate//! questions.//!//! The values follow the rules of the [Python 3.14 language//! reference](https://docs.python.org/3.14/reference/), and the file's tests check them: Python's//! truth rules, its integer reading of booleans, its equality for strings, lists, tuples and//! ranges, and equal hashes for equal keys.//!//! Python's integers grow without bound, and this package stores an integer in 128 signed bits, so//! arithmetic that leaves that range fails with `IntegerOverflow`. A string is a borrowed slice of//! UTF-8 bytes that points into the source text or into the heap. Length, iteration and slicing of//! a string count codepoints. A function value is the position of its body in the top-level chunk,//! and a builtin value is a tag that names one of nine builtin functions. A dictionary keeps its//! entries in an array in insertion order and maps each key to its entry's position in a separate//! hash table, so lookup goes by hash and iteration goes by insertion order. Removing a dictionary//! entry shifts every later entry down one position and updates each later entry's position in the//! hash table, so removal takes time linear in the number of later entries. `hash` uses Wyhash with//! a fixed seed, so a value hashes the same in every run. `hash` reads a boolean as its integer, so//! equal keys share a hash. Each iterator is a small record that points to what it walks and holds//! a position, with a flag for walking from the end.const std = @import("std");/// One Python value, as a tagged union of thirteen kinds. The virtual machine reads and writes/// values in every operation, and `Result.value` is one. `None`, booleans and integers live in the/// value itself, a string is a slice of bytes held elsewhere, and every other kind points to an/// object held elsewhere. The objects that the virtual machine makes live on its heap. Copying a/// value copies the pointer, so both copies refer to the same object.pub const Value = union(enum) { /// Python's `None`, with no payload. `None` is false by Python's truth rules and equal only to /// `None`. none, /// Python's `True` or `False`. A boolean counts as the integer 1 or 0 in arithmetic, equality, /// ordering and hashing, so `True == 1` holds and a dictionary finds the key `1` under `True`. /// A boolean is never the same object as an integer under `is`. boolean: bool, /// A Python integer, stored in 128 signed bits. Arithmetic that leaves that range fails with /// `IntegerOverflow`. integer: i128, /// A Python string, as a slice of UTF-8 bytes that the value borrows. A string literal and its /// slices with step 1 point into the source text, and a string the run built points into the /// heap. Two strings are equal when their bytes are equal. Two strings are the same object /// under `is` when they start at the same address and have the same length. string: []const u8, /// A Python function, as the position of its body in the function table of the top-level chunk. /// The position names nothing once that chunk is freed. function: usize, /// One of the nine builtin functions, named by a `Builtin` tag. builtin: Builtin, /// A list or dictionary method bound to the object it was read from, as a pointer to a /// `NativeMethod` on the heap. method: *NativeMethod, /// A pointer to a Python list. When the virtual machine made the list, the machine's heap owns /// it. list: *List, /// A pointer to a Python tuple. When the virtual machine made the tuple, the machine's heap /// owns it. tuple: *Tuple, /// A pointer to a Python dictionary. When the virtual machine made the dictionary, the /// machine's heap owns it. dict: *Dict, /// A pointer to a view of a dictionary's keys, values or items. view: *DictView, /// A pointer to a Python range. range: *Range, /// A pointer to an iterator, whose position advances as it yields items. iterator: *Iterator, /// Returns whether the value is true by Python's truth rules, for `if`, `while`, `and`, `or` /// and `not`. `None`, `False`, zero, and empty strings, lists, tuples, dictionaries, dictionary /// views and ranges are false. A dictionary view is false when its dictionary is empty. /// Functions, builtins, bound methods and iterators are always true. pub fn truthy(self: Value) bool { return switch (self) { .none => false, .boolean => |value| value, .integer => |value| value != 0, .string => |value| value.len != 0, .function => true, .builtin => true, .method => true, .list => |value| value.items.len != 0, .tuple => |value| value.items.len != 0, .dict => |value| value.entries.items.len != 0, .view => |value| value.dict.entries.items.len != 0, .range => |value| value.length != 0, .iterator => true, }; } /// Returns the integer a value stands for: an integer's own value, 1 for `True`, 0 for `False`, /// and `null` for every other kind. Arithmetic, indexing, `range`, `enumerate` and repetition /// read their integer inputs through it. pub fn integerLike(self: Value) ?i128 { return switch (self) { .integer => |value| value, .boolean => |value| if (value) 1 else 0, .none => null, .string => null, .function => null, .builtin => null, .method => null, .list => null, .tuple => null, .dict => null, .view => null, .range => null, .iterator => null, }; } /// Returns whether the value may be a dictionary key, so the virtual machine checks it before /// every dictionary lookup and insertion. `None`, booleans, integers, strings, functions, /// builtins, bound methods and ranges may be keys. A tuple may be a key when every item may. /// Lists, dictionaries, dictionary views and iterators may never be keys. The check walks a /// tuple item by item, so its cost grows with the tuple. pub fn hashable(self: Value) bool { return switch (self) { .none, .boolean, .integer, .string, .function, .builtin, .method, .range => true, .tuple => |value| sequenceHashable(value.items), .list, .dict, .view, .iterator => false, }; } /// Returns whether two values are equal by Python's rules, for `==`, `!=`, membership tests and /// dictionary lookups. Integers and booleans compare by their integer value. Strings compare /// their bytes, and lists and tuples compare item by item. Two dictionaries are equal when they /// hold equal values under the same keys, in any order. Two ranges are equal when they yield /// the same elements. Keys views compare their dictionaries' keys, items views compare their /// dictionaries, and a values view equals only itself. Two bound methods are equal when they /// have the same method and the same receiver. Functions and builtins compare their positions /// and tags, and iterators are equal only to themselves. Values of unrelated kinds are unequal, /// and the comparison returns no error. Comparing a list with itself returns true before /// comparing its items. Comparing a tuple with itself returns true before comparing its items. /// A list containing itself therefore equals itself, including when it holds a tuple containing /// that same list. Two distinct lists or dictionaries that each contain themselves recurse /// until the native stack overflows when comparison reaches those self-references because /// comparison has no depth bound. pub fn eql(self: Value, other: Value) bool { if (self.integerLike()) |left| { if (other.integerLike()) |right| return left == right; } return switch (self) { .none => other == .none, .boolean => false, .integer => false, .string => |left| switch (other) { .string => |right| std.mem.eql(u8, left, right), else => false, }, .function => |left| switch (other) { .function => |right| left == right, else => false, }, .builtin => |left| switch (other) { .builtin => |right| left == right, else => false, }, .method => |left| switch (other) { .method => |right| nativeMethodsEqual(left, right), else => false, }, .list => |left| switch (other) { .list => |right| listsEqual(left, right), else => false, }, .tuple => |left| switch (other) { .tuple => |right| left == right or sequencesEqual(left.items, right.items), else => false, }, .dict => |left| switch (other) { .dict => |right| dictsEqual(left, right), else => false, }, .view => |left| switch (other) { .view => |right| dictViewsEqual(left, right), else => false, }, .range => |left| switch (other) { .range => |right| rangesEqual(left, right), else => false, }, .iterator => |left| switch (other) { .iterator => |right| left == right, else => false, }, }; } /// Returns a 64-bit Wyhash of the value, with a fixed seed, so a value hashes the same in every /// run. The dictionary's hash table hashes keys with it. Equal values hash equally: an integer /// and the boolean with its value share a hash, and so do equal ranges and equal tuples. A /// bound method hashes its method and its receiver's address. The function asserts that the /// value is hashable, so the caller checks `hashable` first. pub fn hash(self: Value) u64 { std.debug.assert(self.hashable()); var hasher = std.hash.Wyhash.init(0x5059_5448_4f4e_5641); hashInto(&hasher, self); return hasher.final(); }};/// The builtin functions that the package provides, one tag each, named after the function: `dict`,/// `enumerate`, `iter`, `len`, `list`, `next`, `range`, `reversed` and `tuple`. The virtual machine/// pushes one when a name matches no local or global variable. Calling a builtin runs it inside the/// virtual machine and pushes its result.pub const Builtin = enum { dict, enumerate, iter, len, list, next, range, reversed, tuple,};/// A list or dictionary method bound to the object it was read from, as a tagged union with one tag/// per method. Reading a method of a list or dictionary makes one, and calling it runs the method./// Each tag's payload points to that object. Each tag is named after the object's type and the/// method: `dict_clear`, `dict_copy`, `dict_get`, `dict_items`, `dict_keys`, `dict_pop`,/// `dict_popitem`, `dict_setdefault`, `dict_update`, `dict_values`, `list_append`, `list_clear`,/// `list_copy` and `list_pop`. Two bound methods are equal when their tags and objects match. Each/// read makes a new bound method, so two reads give equal values that are different objects. A/// bound method is hashable, so it can be a dictionary key.pub const NativeMethod = union(enum) { dict_clear: *Dict, dict_copy: *Dict, dict_get: *Dict, dict_items: *Dict, dict_keys: *Dict, dict_pop: *Dict, dict_popitem: *Dict, dict_setdefault: *Dict, dict_update: *Dict, dict_values: *Dict, list_append: *List, list_clear: *List, list_copy: *List, list_pop: *List,};/// A Python list, as a Zig growable array of values. The virtual machine's lists are these, and the/// package's property test and benchmark drive one directly. The array grows geometrically, so/// filling it takes few allocations. After `clearRetainingCapacity`, refilling the array to its old/// length allocates nothing and keeps the same storage. The virtual machine's `clear` method frees/// the storage. When `pop` or `del` leaves fewer than half of the slots in use, the virtual machine/// shrinks the storage to fit the items.pub const List = std.ArrayListUnmanaged(Value);/// A Python tuple, as a fixed slice of values. Tuple displays, `tuple()` and the pairs that/// dictionaries and `enumerate` yield are these. The virtual machine never changes a tuple after/// making it, so a tuple of hashable items can be a dictionary key.pub const Tuple = struct { /// The tuple's values, in order. When the virtual machine made the tuple, the machine's heap /// owns this array. items: []Value,};/// A Python dictionary that keeps insertion order: an array of key and value entries, in the order/// the keys first arrived, and a hash table from each key to its entry's position. Dictionary/// displays, `dict()` and `copy()` make these, and the package's property test and benchmark drive/// one directly. An empty dictionary is `.{}`. Finding a key and replacing its value take expected/// constant time and allocate nothing. Removing an entry shifts every later entry down one position/// and updates each later entry's position in the hash table, so removal takes time linear in the/// number of later entries. The hash table grows when it is more than 80 percent full. Every key/// has to be hashable, and `put` asserts it.pub const Dict = struct { /// The key and value pairs in insertion order. Iteration, `popLast` and equality read this /// array. entries: std.ArrayListUnmanaged(DictEntry) = .empty, /// A hash table from each key to its entry's position in `entries`. The table hashes keys with /// `Value.hash` and compares them with `Value.eql`. The table's type is private to this file, /// so callers reach it through the methods below. index: DictIndex = .empty, /// Frees the entries and the hash table with the given allocator and leaves the dictionary /// undefined. The heap frees each dictionary it owns with it. The call frees nothing that the /// keys and values point to. pub fn deinit(self: *Dict, allocator: std.mem.Allocator) void { self.index.deinit(allocator); self.entries.deinit(allocator); self.* = undefined; } /// Removes every entry and keeps the memory of both the entries and the hash table for reuse. /// The virtual machine's `dict.clear` method empties a dictionary with it. The call allocates /// nothing, so it cannot fail. pub fn clearRetainingCapacity(self: *Dict) void { self.entries.clearRetainingCapacity(); self.index.clearRetainingCapacity(); } /// Reserves room for at least `capacity` entries in both the entry array and the hash table. /// `createDict`, dictionary displays and the benchmark reserve room with it before inserting /// many keys. The call returns `error.OutOfMemory` when an allocation fails. The call also /// returns `error.OutOfMemory` when `capacity` exceeds the largest 32-bit unsigned integer, /// because the hash table counts its entries in 32 bits. pub fn ensureTotalCapacity(self: *Dict, allocator: std.mem.Allocator, capacity: usize) std.mem.Allocator.Error!void { if (capacity > std.math.maxInt(u32)) return error.OutOfMemory; try self.entries.ensureTotalCapacity(allocator, capacity); try self.index.ensureTotalCapacity(allocator, @intCast(capacity)); } /// Returns the position of the key's entry in `entries`, or null when the key is absent. Every /// dictionary lookup, membership test and deletion in the virtual machine starts with it. The /// key has to be hashable, because `Value.hash` asserts it. pub fn indexOf(self: *const Dict, key: Value) ?usize { return self.index.get(key); } /// When an equal key is present, replaces that entry's value, and the entry keeps its position /// and its first key. Any other key goes with its value into a new entry at the end. Every /// dictionary insertion in the virtual machine goes through it. Replacing a value allocates /// nothing. The call asserts that the key is hashable. On `error.OutOfMemory`, both the entry /// array and the hash table stay as they were. pub fn put(self: *Dict, allocator: std.mem.Allocator, key: Value, entry_value: Value) std.mem.Allocator.Error!void { std.debug.assert(key.hashable()); if (self.index.get(key)) |entry_index| { self.entries.items[entry_index].value = entry_value; return; } const entry_index = self.entries.items.len; try self.entries.append(allocator, .{ .key = key, .value = entry_value }); errdefer self.entries.items.len -= 1; try self.index.put(allocator, key, entry_index); } /// Removes the entry at the given position and returns its value. `del d[k]` and `d.pop(k)` /// remove an entry with it. The call shifts every later entry down one place and updates each /// later entry's position in the hash table. The call takes time linear in the number of /// entries after the position. The position has to be in range. The call allocates nothing, so /// it cannot fail. pub fn removeAt(self: *Dict, entry_index: usize) Value { const removed = self.entries.orderedRemove(entry_index); std.debug.assert(self.index.remove(removed.key)); for (self.entries.items[entry_index..], entry_index..) |entry, index| { self.index.getPtr(entry.key).?.* = index; } return removed.value; } /// Removes the most recently inserted entry and returns it, or returns null when the dictionary /// is empty. `d.popitem()` removes the newest entry with it. The call leaves every other entry /// at its position. pub fn popLast(self: *Dict) ?DictEntry { const entry = self.entries.pop() orelse return null; std.debug.assert(self.index.remove(entry.key)); return entry; }};/// One key and its value in a dictionary. A dictionary's entry array holds these, and `createDict`/// takes a slice of them.pub const DictEntry = struct { /// The entry's key, which is hashable. key: Value, /// The value stored under the key. value: Value,};/// The part of a dictionary a view shows, one tag each, named after the Python method that returns/// the view: `keys`, `values` and `items`. A dictionary view carries one. An items view yields each/// entry as a two-item tuple of key and value.pub const DictViewKind = enum { keys, values, items,};/// A live view of one dictionary. `keys()`, `values()` and `items()` return one. The view borrows/// the dictionary and shows every later change to it. A view's length and truth value are those of/// its dictionary. A keys view and an items view support membership and equality by content, and a/// values view equals only itself.pub const DictView = struct { /// The dictionary that the view shows. The view borrows this dictionary. dict: *Dict, /// Which part of the dictionary the view shows. kind: DictViewKind,};/// A Python range: the integers from `start` toward `stop` in steps of `step`, with the count of/// elements stored. `range()` and range slices make one. Membership is computed from `start`,/// `stop` and `step`, in constant time. Two ranges are equal when they yield the same elements,/// whatever their stops.pub const Range = struct { /// The first element. start: i128, /// The bound the elements approach and never reach. stop: i128, /// The difference between neighboring elements. `range` and slicing reject a step of zero with /// `ValueError`. step: i128, /// The number of elements, computed once by `range` or by a slice. length: usize,};/// The state of one walk for `for` loops, `iter`, `next`, `enumerate`, `reversed`, and the/// consuming builtins, as one tag per kind of source: a dictionary's keys, `enumerate`, a/// dictionary view, a list, a tuple, a range, or a string. Each `next` call or loop step advances/// the walk, so items it passed are gone for every holder of the iterator. Each kind borrows what/// it walks, so a list or dictionary changed during the walk is read as it stands at each step. An/// iterator has no hash, so it cannot be a dictionary key. An iterator equals only itself.pub const Iterator = union(enum) { /// Walks a dictionary's keys. dict: DictIterator, /// Pairs the items of another iterator with a counter. enumerate: EnumerateIterator, /// Walks a dictionary view. view: DictViewIterator, /// Walks a list. list: ListIterator, /// Walks a tuple. tuple: TupleIterator, /// Walks a range. range: RangeIterator, /// Walks a string by codepoint. string: StringIterator,};/// The state of a walk over a list, from the first item or from the last, for `createListIterator`/// and `createListReverseIterator`. The walk ends when its count of items taken reaches the list's/// current length.pub const ListIterator = struct { /// The borrowed list that the walk reads. list: *List, /// How many items the walk has taken, starting at 0. A walk from the end reads the item that /// many places before the last. index: usize = 0, /// True for a walk from the last item to the first, and false by default. reverse: bool = false,};/// The state of a walk over a dictionary's keys, in insertion order or from the newest key back,/// for `createDictIterator` and `createDictReverseIterator`. The walk checks nothing when the/// dictionary changes under it, so a key added or removed mid-walk shifts what the walk yields.pub const DictIterator = struct { /// The borrowed dictionary that the walk reads. dict: *Dict, /// How many keys the walk has taken, starting at 0. index: usize = 0, /// True for a walk from the newest key to the oldest, and false by default. reverse: bool = false,};/// The state of an `enumerate` walk for `createEnumerateIterator`: an inner iterator and the/// counter value its next item gets. Each step makes a new two-item tuple on the heap.pub const EnumerateIterator = struct { /// The borrowed inner iterator, which each step advances by one item. iterator: *Iterator, /// The counter value for the next item. The counter starts at the `start` argument of /// `enumerate`. index: i128, /// Set once the counter has reached the largest signed 128-bit integer. After that, one more /// item from the inner iterator fails with `IntegerOverflow`, and an exhausted inner iterator /// ends the walk. overflowed: bool = false,};/// The state of a walk over a dictionary view, for `createDictViewIterator` and/// `createDictViewReverseIterator`: its keys, its values, or its entries as key and value tuples. A/// walk over an items view makes a new tuple on the heap for each entry.pub const DictViewIterator = struct { /// The borrowed view that the walk reads. view: *DictView, /// How many entries the walk has taken, starting at 0. index: usize = 0, /// True for a walk from the newest entry to the oldest, and false by default. reverse: bool = false,};/// The state of a walk over a tuple, from the first item or from the last, for/// `createTupleIterator` and `createTupleReverseIterator`.pub const TupleIterator = struct { /// The borrowed tuple that the walk reads. tuple: *Tuple, /// How many items the walk has taken, starting at 0. index: usize = 0, /// True for a walk from the last item to the first, and false by default. reverse: bool = false,};/// The state of a walk over a range's elements, from its start or from its last element, for/// `createRangeIterator` and `createRangeReverseIterator`. The walk fails with `IntegerOverflow`/// when the next element leaves the signed 128-bit range.pub const RangeIterator = struct { /// The borrowed range that the walk reads. range: *Range, /// How many elements the walk has yielded, starting at 0. The walk ends when this count reaches /// the range's length. A walk from the end computes each element from this count. index: usize = 0, /// The element that a forward walk yields next. A new iterator holds the range's start here. A /// walk from the end leaves it unused. next: i128, /// True for a walk from the last element to the start, and false by default. reverse: bool = false,};/// The state of a walk over a string's codepoints, from the first or from the last, for/// `createStringIterator` and `createStringReverseIterator`. Each step yields one codepoint as a/// string that points into the same bytes. The walk fails with `ValueError` when the bytes contain/// invalid UTF-8. Each step checks bytes for valid UTF-8 again: a forward walk checks the rest of/// the string, and a walk from the end checks all of it. A full walk therefore takes time quadratic/// in the string's length.pub const StringIterator = struct { /// The borrowed bytes of the string that the walk reads. value: []const u8, /// The byte offset of the walk's position in the string. A forward walk starts this offset at 0 /// and moves it forward. A walk from the end starts this offset at the string's length and /// moves it back. index: usize = 0, /// True for a walk from the last codepoint to the first, and false by default. reverse: bool = false,};const ValueContext = struct { pub fn hash(_: ValueContext, value: Value) u64 { return value.hash(); } pub fn eql(_: ValueContext, left: Value, right: Value) bool { return left.eql(right); }};const DictIndex = std.HashMapUnmanaged(Value, usize, ValueContext, 80);fn hashInto(hasher: *std.hash.Wyhash, value: Value) void { if (value.integerLike()) |integer| { hashTag(hasher, 1); hasher.update(std.mem.asBytes(&integer)); return; } switch (value) { .none => hashTag(hasher, 0), .boolean, .integer => unreachable, .string => |string| { hashTag(hasher, 2); hasher.update(std.mem.asBytes(&string.len)); hasher.update(string); }, .function => |function| { hashTag(hasher, 3); hasher.update(std.mem.asBytes(&function)); }, .builtin => |builtin| { hashTag(hasher, 4); const tag: u8 = @backingInt(builtin); hasher.update(&.{tag}); }, .method => |method| { hashTag(hasher, 5); hashNativeMethod(hasher, method); }, .tuple => |tuple| { hashTag(hasher, 6); hasher.update(std.mem.asBytes(&tuple.items.len)); for (tuple.items) |item| hashInto(hasher, item); }, .range => |range| { hashTag(hasher, 7); hasher.update(std.mem.asBytes(&range.length)); if (range.length != 0) hasher.update(std.mem.asBytes(&range.start)); if (range.length > 1) hasher.update(std.mem.asBytes(&range.step)); }, .list, .dict, .view, .iterator => unreachable, }}fn hashTag(hasher: *std.hash.Wyhash, tag: u8) void { hasher.update(&.{tag});}fn hashNativeMethod(hasher: *std.hash.Wyhash, method: *const NativeMethod) void { const tag: u8 = @backingInt(std.meta.activeTag(method.*)); hasher.update(&.{tag}); const owner = switch (method.*) { .dict_clear => |dict| @intFromPtr(dict), .dict_copy => |dict| @intFromPtr(dict), .dict_get => |dict| @intFromPtr(dict), .dict_items => |dict| @intFromPtr(dict), .dict_keys => |dict| @intFromPtr(dict), .dict_pop => |dict| @intFromPtr(dict), .dict_popitem => |dict| @intFromPtr(dict), .dict_setdefault => |dict| @intFromPtr(dict), .dict_update => |dict| @intFromPtr(dict), .dict_values => |dict| @intFromPtr(dict), .list_append => |list| @intFromPtr(list), .list_clear => |list| @intFromPtr(list), .list_copy => |list| @intFromPtr(list), .list_pop => |list| @intFromPtr(list), }; hasher.update(std.mem.asBytes(&owner));}fn listsEqual(left: *const List, right: *const List) bool { if (left == right) return true; return sequencesEqual(left.items, right.items);}fn sequencesEqual(left: []const Value, right: []const Value) bool { if (left.len != right.len) return false; for (left, right) |a, b| { if (!a.eql(b)) return false; } return true;}fn sequenceHashable(values: []const Value) bool { for (values) |item| { if (!item.hashable()) return false; } return true;}fn nativeMethodsEqual(left: *const NativeMethod, right: *const NativeMethod) bool { return switch (left.*) { .dict_clear => |dict| right.* == .dict_clear and right.dict_clear == dict, .dict_copy => |dict| right.* == .dict_copy and right.dict_copy == dict, .dict_get => |dict| right.* == .dict_get and right.dict_get == dict, .dict_items => |dict| right.* == .dict_items and right.dict_items == dict, .dict_keys => |dict| right.* == .dict_keys and right.dict_keys == dict, .dict_pop => |dict| right.* == .dict_pop and right.dict_pop == dict, .dict_popitem => |dict| right.* == .dict_popitem and right.dict_popitem == dict, .dict_setdefault => |dict| right.* == .dict_setdefault and right.dict_setdefault == dict, .dict_update => |dict| right.* == .dict_update and right.dict_update == dict, .dict_values => |dict| right.* == .dict_values and right.dict_values == dict, .list_append => |list| right.* == .list_append and right.list_append == list, .list_clear => |list| right.* == .list_clear and right.list_clear == list, .list_copy => |list| right.* == .list_copy and right.list_copy == list, .list_pop => |list| right.* == .list_pop and right.list_pop == list, };}fn dictsEqual(left: *const Dict, right: *const Dict) bool { if (left == right) return true; if (left.entries.items.len != right.entries.items.len) return false; for (left.entries.items) |entry| { const index = right.indexOf(entry.key) orelse return false; if (!entry.value.eql(right.entries.items[index].value)) return false; } return true;}fn dictViewsEqual(left: *const DictView, right: *const DictView) bool { if (left == right) return true; if (left.kind != right.kind) return false; return switch (left.kind) { .keys => dictKeysEqual(left.dict, right.dict), .items => dictsEqual(left.dict, right.dict), .values => false, };}fn dictKeysEqual(left: *const Dict, right: *const Dict) bool { if (left.entries.items.len != right.entries.items.len) return false; for (left.entries.items) |entry| { if (right.indexOf(entry.key) == null) return false; } return true;}fn rangesEqual(left: *const Range, right: *const Range) bool { if (left.length != right.length) return false; if (left.length == 0) return true; if (left.start != right.start) return false; if (left.length == 1) return true; return left.step == right.step;}test "boolean values behave as Python integers in arithmetic contexts" { try std.testing.expectEqual(@as(?i128, 1), (Value{ .boolean = true }).integerLike()); try std.testing.expectEqual(@as(?i128, 0), (Value{ .boolean = false }).integerLike());}test "equality follows Python integer boolean behavior" { try std.testing.expect((Value{ .boolean = true }).eql(.{ .integer = 1 })); try std.testing.expect((Value{ .boolean = false }).eql(.{ .integer = 0 })); try std.testing.expect((Value{ .none = {} }).eql(.none)); try std.testing.expect(!(Value{ .none = {} }).eql(.{ .integer = 0 }));}test "string values have Python truthiness and equality" { try std.testing.expect(!(Value{ .string = "" }).truthy()); try std.testing.expect((Value{ .string = "x" }).truthy()); try std.testing.expect((Value{ .string = "alpha" }).eql(.{ .string = "alpha" })); try std.testing.expect(!(Value{ .string = "alpha" }).eql(.{ .string = "beta" }));}test "list values have Python truthiness and equality" { var left_items = [_]Value{ .{ .integer = 1 }, .{ .string = "x" } }; var right_items = [_]Value{ .{ .integer = 1 }, .{ .string = "x" } }; var different_items = [_]Value{.{ .integer = 1 }}; var empty: List = .empty; var left = List{ .items = &left_items, .capacity = left_items.len }; var right = List{ .items = &right_items, .capacity = right_items.len }; var different = List{ .items = &different_items, .capacity = different_items.len }; try std.testing.expect(!(Value{ .list = &empty }).truthy()); try std.testing.expect((Value{ .list = &left }).truthy()); try std.testing.expect((Value{ .list = &left }).eql(.{ .list = &right })); try std.testing.expect(!(Value{ .list = &left }).eql(.{ .list = &different }));}test "tuple values have Python truthiness and equality" { var left_items = [_]Value{ .{ .integer = 1 }, .{ .string = "x" } }; var right_items = [_]Value{ .{ .integer = 1 }, .{ .string = "x" } }; var different_items = [_]Value{.{ .integer = 1 }}; var empty = Tuple{ .items = &.{} }; var left = Tuple{ .items = &left_items }; var right = Tuple{ .items = &right_items }; var different = Tuple{ .items = &different_items }; try std.testing.expect(!(Value{ .tuple = &empty }).truthy()); try std.testing.expect((Value{ .tuple = &left }).truthy()); try std.testing.expect((Value{ .tuple = &left }).eql(.{ .tuple = &right })); try std.testing.expect(!(Value{ .tuple = &left }).eql(.{ .tuple = &different }));}test "range values have Python truthiness and equality" { var empty = Range{ .start = 0, .stop = 0, .step = 1, .length = 0 }; var single = Range{ .start = 1, .stop = 2, .step = 1, .length = 1 }; var same_single = Range{ .start = 1, .stop = 20, .step = 3, .length = 1 }; var left = Range{ .start = 0, .stop = 3, .step = 2, .length = 2 }; var right = Range{ .start = 0, .stop = 4, .step = 2, .length = 2 }; var different = Range{ .start = 0, .stop = 5, .step = 2, .length = 3 }; try std.testing.expect(!(Value{ .range = &empty }).truthy()); try std.testing.expect((Value{ .range = &single }).truthy()); try std.testing.expect((Value{ .range = &single }).eql(.{ .range = &same_single })); try std.testing.expect((Value{ .range = &left }).eql(.{ .range = &right })); try std.testing.expect(!(Value{ .range = &left }).eql(.{ .range = &different }));}test "iterator values are truthy and compare by identity" { var list: List = .empty; var left = Iterator{ .list = .{ .list = &list } }; var right = Iterator{ .list = .{ .list = &list } }; try std.testing.expect((Value{ .iterator = &left }).truthy()); try std.testing.expect((Value{ .iterator = &left }).eql(.{ .iterator = &left })); try std.testing.expect(!(Value{ .iterator = &left }).eql(.{ .iterator = &right }));}test "equal hashable values share hashes" { var left_tuple_items = [_]Value{ .{ .boolean = true }, .{ .string = "tuple" } }; var right_tuple_items = [_]Value{ .{ .integer = 1 }, .{ .string = "tuple" } }; var left_tuple = Tuple{ .items = &left_tuple_items }; var right_tuple = Tuple{ .items = &right_tuple_items }; var left_empty_range = Range{ .start = 4, .stop = 4, .step = 1, .length = 0 }; var right_empty_range = Range{ .start = 90, .stop = -10, .step = -5, .length = 0 }; var left_list: List = .empty; var left_method = NativeMethod{ .list_append = &left_list }; var right_method = NativeMethod{ .list_append = &left_list }; const pairs = [_][2]Value{ .{ .{ .boolean = true }, .{ .integer = 1 } }, .{ .{ .string = "same" }, .{ .string = "same" } }, .{ .{ .tuple = &left_tuple }, .{ .tuple = &right_tuple } }, .{ .{ .range = &left_empty_range }, .{ .range = &right_empty_range } }, .{ .{ .method = &left_method }, .{ .method = &right_method } }, }; for (pairs) |pair| { try std.testing.expect(pair[0].eql(pair[1])); try std.testing.expectEqual(pair[0].hash(), pair[1].hash()); }}test "list geometric storage supports allocation-free retained refill" { var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var list: List = .empty; defer list.deinit(failing.allocator()); for (0..4096) |index| try list.append(failing.allocator(), .{ .integer = @intCast(index) }); const capacity = list.capacity; const pointer = list.items.ptr; const growth_attempts = failing.alloc_index + failing.resize_index; try std.testing.expect(capacity >= list.items.len); try std.testing.expect(growth_attempts < 64); list.clearRetainingCapacity(); failing.fail_index = failing.alloc_index; failing.resize_fail_index = failing.resize_index; for (0..4096) |index| try list.append(failing.allocator(), .{ .integer = @intCast(index) }); try std.testing.expect(!failing.has_induced_failure); try std.testing.expectEqual(capacity, list.capacity); try std.testing.expectEqual(pointer, list.items.ptr);}test "dictionary hashes equality while retaining insertion order" { var dict: Dict = .{}; defer dict.deinit(std.testing.allocator); try dict.put(std.testing.allocator, .{ .boolean = true }, .{ .string = "first" }); try dict.put(std.testing.allocator, .{ .integer = 2 }, .{ .string = "second" }); try dict.put(std.testing.allocator, .{ .integer = 1 }, .{ .string = "updated" }); try std.testing.expectEqual(@as(usize, 2), dict.entries.items.len); try std.testing.expect(dict.entries.items[0].key == .boolean); try std.testing.expectEqualStrings("updated", dict.entries.items[dict.indexOf(.{ .integer = 1 }).?].value.string); _ = dict.removeAt(dict.indexOf(.{ .integer = 1 }).?); try dict.put(std.testing.allocator, .{ .boolean = true }, .{ .string = "reinserted" }); try std.testing.expectEqual(@as(i128, 2), dict.entries.items[0].key.integer); try std.testing.expect(dict.entries.items[1].key == .boolean); try std.testing.expectEqual(@as(usize, 0), dict.indexOf(.{ .integer = 2 }).?); try std.testing.expectEqual(@as(usize, 1), dict.indexOf(.{ .integer = 1 }).?);}test "dictionary insertion failure leaves both stores unchanged" { var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var dict: Dict = .{}; defer dict.deinit(failing.allocator()); try dict.entries.ensureTotalCapacity(failing.allocator(), 1); failing.fail_index = failing.alloc_index; try std.testing.expectError(error.OutOfMemory, dict.put(failing.allocator(), .{ .integer = 7 }, .none)); try std.testing.expectEqual(@as(usize, 0), dict.entries.items.len); try std.testing.expectEqual(@as(u32, 0), dict.index.count()); failing.fail_index = std.math.maxInt(usize); try dict.put(failing.allocator(), .{ .integer = 7 }, .none); try std.testing.expectEqual(@as(usize, 0), dict.indexOf(.{ .integer = 7 }).?);}test "dictionary existing updates allocate no working memory" { var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var dict: Dict = .{}; defer dict.deinit(failing.allocator()); for (0..1024) |index| try dict.put(failing.allocator(), .{ .integer = @intCast(index) }, .none); failing.fail_index = failing.alloc_index; failing.resize_fail_index = failing.resize_index; for (0..1024) |index| { const key = Value{ .integer = @intCast(index) }; try dict.put(failing.allocator(), key, key); try std.testing.expectEqual(index, dict.indexOf(key).?); } try std.testing.expect(!failing.has_induced_failure);}Audit
| Definitions | 25 |
|---|---|
| Public names | 41 |
| Members | 66 |
| Version | 26.7.0 |
| Revision | daab053ee433 |