Back to Monty

Built-in functions

limitations/builtins.md

0.0.217.0 KB
Original Source

Built-in functions

Monty implements a deliberate subset of CPython's builtins. Referencing any name not listed here raises NameError at runtime — there is no fallback to a host Python.

Implemented builtin functions

abs, all, any, bin, chr, divmod, enumerate, filter, getattr, hasattr, hash, hex, id, isinstance, iter, len, map, max, min, next, oct, open, ord, pow, print, repr, reversed, round, setattr, sorted, sum, type, zip.

Implemented type constructors (also builtins)

bool, bytes, dict, float, frozenset, int, list, range, set, slice, str, tuple. Exception classes (ValueError, TypeError, etc.) are also names in the builtin namespace.

Builtins that are NOT implemented

These raise NameError:

  • Code execution: eval, exec, compile, __import__. Deliberate — sandboxed code must not be able to compile new code at runtime.
  • Namespace introspection: globals, locals, vars, dir.
  • Interactive: input, breakpoint, help.
  • Decorators / descriptors: classmethod, staticmethod, property, super. (@property on functions is not recognized; use a method.)
  • Construction / coercion: bytearray, complex, memoryview, object, format, ascii.
  • Other: callable, delattr, issubclass, aiter, anext.

super() is the biggest practical omission — combined with the lack of class statements (see language.md) there is no inheritance mechanism beyond dataclass field inheritance.

Behavioural divergences

  • enumerate, zip, map, filter and reversed are eager, not lazy — each drains its source and returns a list, so type(enumerate(x)).__name__ is 'list' rather than 'enumerate'. Observable several ways: a side-effecting callable runs for every item at the call itself rather than as the result is consumed; the whole result is held in memory at once, so an infinite iterator (e.g. map(f, itertools.count())) never returns and runs until a resource limit trips; the result can be indexed and re-iterated, which CPython forbids; and mutating the source from inside the loop body is never observed, so containers that detect mutation during iteration (dict, set, collections.deque) will not raise when looped over via one of these. zip and multi-iterable map stop at the shortest input, so pairing an infinite iterable with a finite or empty one stays bounded. A plain for x in container is lazy and does detect mutation. See itertools.md.
  • Arity-error wording for some str/bytes methods — a handful of keyword-accepting methods (e.g. str.split, str.rsplit and the bytes equivalents) report too-many-arguments as split expected at most 2 arguments, got 3, where CPython 3.14's Argument Clinic pre-counts positionals plus kwargs and says split() takes at most 2 arguments (3 given). Methods audited against CPython (encode, decode, expandtabs, splitlines, replace, …) already match; the remainder need a per-function at_most_total audit.
  • getattr(obj, name) — if the resolved attribute would be an async coroutine, external function, or OS call, raises TypeError: "getattr(): attribute is not a simple value" rather than returning a bound method object. Use direct attribute access (obj.name(...)) for these.
  • int(x, base=10) — string/bytes parsing accepts ASCII digits only; CPython also accepts non-ASCII Unicode decimal digits (int('١٢') == 12), which Monty rejects with invalid literal for int() with base 10.
  • bytes(source) — an iterable of ints is not supported: CPython's bytes([65, 66]) == b'AB', Monty raises TypeError: cannot convert 'list' object to bytes. The int / str-with-encoding / bytes source forms all work.
  • isinstance(obj, T)T must be a built-in type (int, str, list, ...), a built-in exception class, a sandbox-defined class (see classes.md), or a tuple of those. Passing a host-supplied dataclass / namedtuple as the second argument raises TypeError.
  • iter() — see iter.md for iterator and iter(callable, sentinel) divergences.
  • pow(base, exp, mod) — three-argument form requires all integers and rejects negative exponents with ValueError instead of computing a modular inverse. Non-modular exponents whose result cannot be materialized raise OverflowError (see resource_limits.md).
  • sorted(iterable, *, key=None, reverse=False)key and reverse must be passed by keyword; positional forms raise TypeError.
  • round(n, ndigits)ndigits values outside the i64 range are clamped by sign. For floats this matches CPython (which clamps to Py_ssize_t); for an int n with a hugely negative ndigits, CPython tries to materialise 10**-ndigits and dies with MemoryError where Monty returns 0 immediately.
  • print — writes via the host print callback. file=, flush= are not honoured; sep= and end= are.
  • Identity of host-supplied callables — host functions passed in as inputs (MontyObject::Function) lose their host object identity at the sandbox boundary. Live external functions are identified by lookup name, so distinct host callables with the same name share is, equality, id(), and hash() results. Once the last sandbox reference is dropped, a later conversion of that name may create a new function object.
  • Type objects across the host boundary — a type object (a class, not an instance) round-trips in both directions.
    • Sandbox → host (external/OS-call argument, or a .run() return value): the type is reconstructed as the corresponding host class. Genuine builtins (int, str, type, bytes, list, dict, property, …) resolve to the real builtin; Monty's modeled stdlib types map to their host stdlib class: datetime/date/timedelta/timezonedatetime.*, re.Pattern/re.Matchre.*, the binary/text file types → io.*. The pathlib.Path class maps to pathlib.PurePosixPath (consistent with how Path instances round-trip, and instantiable on every host OS). A type with no faithful host class (e.g. an internal function or cell type) cannot be reconstructed and surfaces as an AttributeError from the host call.
    • Host → sandbox (input, or an external-call return value): the same recognized builtins and modeled stdlib types are preserved as type objects, so isinstance(x, the_type) works inside the sandbox. Recognition is by type-object identity, not class name/module, so a class that forges __name__/__module__ to impersonate a builtin is not treated as one. Every pathlib path class collapses to PurePosixPath (it re-emerges as PurePosixPath). A host class Monty does not model (e.g. a user-defined class) is not preserved as a type — it degrades to a callable, appearing inside the sandbox as a function rather than a type.