Tomato
A programming language, in design

Tomato.

Easy to use. Typed. Garbage collected. Compiled. Every meaningful rule chosen on purpose.

This is the decision record, not a finished language.
These are the choices Davis McDowell has confirmed so far. Syntax and implementation details are still being designed.
Confirmed directionLiving design · updated November 2026
01 / North star

Simple by default. Control when needed.

Tomato draws on the feel of Python and Go without silently adopting either language's rules. It aims for readable everyday code and explicit options when performance matters.

D04

Typed before running

Known type mismatches in ordinary code are rejected during compilation, including code that would not execute.

R03 · D15

Compiled with LLVM

Garbage collection is required. LLVM is the chosen backend, and WebAssembly output is required for web apps.

R04 · R07

Python-like syntax

That describes the intended feel. Exact grammar, indentation rules, and compatibility are not yet decided.

Performance direction

Faster than Python is the goal. Roughly half Go's speed is acceptable; this is a target to measure, not a benchmark result or a promise for every program.

02 / Built for

Useful programs, from servers to the browser.

The first use cases are web servers; simple integer math such as prime checking and Fibonacci modulo a number; and web apps with parts written in Tomato and compiled to WebAssembly. Their libraries, APIs, and runtime arrangements are still open.

03 / Accepted rules

What has actually been decided.

These rules are accepted by Davis. Illustrative spelling in the design documents does not establish Tomato syntax.

Function types D04b

Ordinary named functions require explicit parameter and return types. Local variable types are inferred where possible. Precise inference rules remain open.

Classes D12a–b

Classes and interfaces are required. A class may inherit fields and methods from one parent class and replace selected inherited methods. Multiple class inheritance is excluded.

Field declarations D12n–o · t · u · v

Support class-body field declarations private name: Type, including private x: int[length=8]. Every instance field must be declared in the class body, and every declaration gives its type; assignment in @init does not create fields and types are not inferred from initial values. Each instance stores its own copy of a field, so changing one instance’s x does not change another’s. Add const to mark a field that cannot be changed after initialization. D12r excludes direct subclass access. Modifier order/placement, initialization values and defaults, shared-field syntax, remaining private access scope, default visibility/mutability, initialization checking, and surrounding class grammar remain open. No separate struct type, deep immutability, or compile-time evaluation is implied. D12n–q were first stated in a shared-password web chat and later re-confirmed by Davis in the byre task.

Method declarations D12p–q

Define class methods with def; a method may also have the modifier private. Place it after def and before the method name: def private name, as in def private x. This is the accepted prefix, not a complete signature. Other modifier order/placement, receiver/parameter/return syntax and annotations, bodies/grammar, default visibility, remaining private-access scope (D12r excludes direct subclass access), dispatch, and inheritance details remain open. This does not import Python method behavior.

Private members and subclasses D12r

A method defined in a subclass cannot directly read or write its parent class’s private fields or call its parent’s private methods. It uses accessible parent methods instead. Default visibility, module/friend access, same-class access through another instance, protected access, lookup/overriding, and error details remain open.

Const object fields D12s

A const field keeps the same object after initialization, while accessible mutable fields and methods on that object remain usable through it. For example, a const owner field cannot be replaced with another Person, but that Person’s mutable name may still change. These names illustrate the rule, not new syntax. Const does not freeze the object or nested objects. Modifier placement/order, initialization/write checking, default mutability, inheritance, and error details remain open.

Initialization D12m

@init marks a class’s initialization function. Its relationship to allocation, instance-versus-factory behavior, function name, signature/receiver, return value, invocation/construction syntax, inheritance, exact marker placement/grammar, and errors still need explicit decisions. Every field must be declared in the class body with its type (D12u–v). No general-purpose decorators or Python initializer behavior are implied.

Custom operators D12j–l

Custom classes will support + using the requested @add spelling for addition customization. Place @add above an ordinary method with a programmer-chosen name: the marker designates that method as the implementation of +. For a + b, invoke the marked instance method on left operand a, passing right operand b as its one other operand argument. The marker is not the method’s name and does not introduce general-purpose decorators. This permits descriptive method names with a separate marker-to-operator relationship. Other operator customization is requested direction; the list and spellings remain open. Receiver spelling, operand/result types, lookup/dispatch, inheritance, mixed-type selection, right-side fallback, mutation, surrounding method grammar, ordinary calls, and error rules still need explicit decisions.

Interfaces D12c–e

A class explicitly declares and is checked for interface conformance; matching methods alone are insufficient. It may implement multiple interfaces. Interfaces have no default method bodies.

Later declarations D12f–i

A separate declaration after a class definition may state that it implements an interface, including when both types are imported. It checks existing methods only; missing methods are a compile error, and it cannot add methods. The relationship applies locally unless exported; another module uses it through an explicit import.

Shared objects D05a–b

Assigning a class instance shares the same object. Passing one to a function shares that object through a local parameter. Allowed mutations are visible to the caller; rebinding the parameter does not replace the caller's variable.

Integers D05c–o

The ordinary integer type is int: signed and growing to arbitrary precision within available resources, with no silent fixed-width wrap or truncation. Optional fixed-width integer types include signed and unsigned forms with programmer-chosen bit lengths known at compile time, rather than a prescribed size list. Widths may use compile-time integer constants and arithmetic. They must be positive whole numbers; zero and negative widths are compile errors. Write the signed type as int[length=x] and the unsigned type as uint[length=x], where x is the bit width. Their arithmetic reports an overflow error rather than wrapping. Signed N-bit values range from −2^(N−1) through 2^(N−1)−1: for 7 bits, −64 through 63; for 1 bit, −1 or 0. Negating the minimum overflows. Unsigned N-bit values range from 0 through 2^N − 1 inclusive: for 7 bits, 0 through 127; for 1 bit, 0 or 1. Arithmetic outside that range, including subtraction below zero, reports overflow. Detailed expression grammar, constant-evaluation rules, resource limits, memory encoding, and error handling remain open.

Integer conversions D05o–q

For already-typed integer assignments, function arguments, and returns, conversion is automatic when the destination covers the source type’s entire range. For example, int[length=7] can become int[length=16], uint[length=7] can become int[length=8], and any fixed-width integer can become growing int. Other conversions between distinct integer types require an explicit request, even if the current value fits. An ordinary explicit conversion reports an error when a runtime value is outside the destination range, rather than wrapping or clamping: converting 300 or −1 to uint[length=8] fails. Write an explicit integer conversion by calling the destination type: int[length=8](value), uint[length=8](value), or int(value). This chooses integer conversion syntax only. Other operators, conversion-operand context, error handling, and analysis outside D05x remain open.

Integer literals D05r · t · v

A direct integer literal takes the expected integer type when its value fits; without an expected integer type, it has growing type int. Passing literal 42 directly to a uint[length=8] parameter needs no explicit conversion. Literal 300 in that position is a compile error. Already-typed values retain the full-source-range conversion rule. A direct literal in addition, subtraction, or multiplication with a typed integer takes that operand’s type if it fits, in either order; the result keeps that type. For an unsigned 8-bit operand, adding literal 1 to a current value of 255 overflows; literal 300 is a compile error. For a single operation with a typed operand, this stays true even with an outside expected type. For one addition, subtraction, or multiplication directly between two literals, use the expected integer type for both operands and result, otherwise growing int. Each literal must fit; the operation still checks overflow. Adding literals 200 and 100 for an unsigned 8-bit destination overflows. D05w extends these rules recursively through literal-only arithmetic. D05x settles constant-failure timing; detailed literal/sign grammar remains open.

Integer arithmetic D05s · u

Addition, subtraction, and multiplication of two already-typed integers use an existing operand type whose range covers both full operand ranges; the result has that type too. Signed 7-bit with signed 16-bit uses int[length=16]; a fixed-width integer with growing int uses int. Signed 8-bit with unsigned 8-bit is a compile error unless explicitly converted, because neither range contains the other. No new common type is synthesized, and fixed-width results still report overflow. A direct literal paired with a typed operand follows D05t. For a single operation with a typed operand, compute from operands first, then convert the result: unsigned 8-bit values holding 200 and 100 overflow on addition even for an unsigned 16-bit destination. Explicitly widen an operand first to obtain 300. A wider destination does not resolve incompatible operands. D05v settles two direct literals. D05w settles nested arithmetic typing. D05y chooses fractional integer /. Remaining division/modulo rules, comparisons, and other operators remain open.

Nested arithmetic D05w

Contextual integer typing extends recursively through literal-only addition, subtraction, and multiplication. A typed sibling supplies the type throughout a literal-only subtree: adding an unsigned 8-bit value to the product of literals 2 and 3 computes product and sum in unsigned 8-bit. A wholly literal expression uses its expected integer type throughout, otherwise growing int. Each intermediate operation checks overflow: with expected unsigned 8-bit, adding 200 and 100 before subtracting 100 still overflows. Subexpressions containing typed values compute independently from their operands; a wider outer type does not retroactively widen them. D05y–aj settle several integer division rules, and D05at settles exact rational addition. Contextual typing for other operators, named-constant context, conversion-operand context, and failure rules outside the accepted constant-error rules remain open.

Constant range errors D05x

Integer overflow and out-of-range conversions evaluable from integer literals, named compile-time integer constants, and built-in integer arithmetic/conversions are compilation errors wherever written, including unreachable ordinary code. Converting literal 300 to uint[length=8], or adding literals 200 and 100 with expected unsigned 8-bit type, is rejected before running. Arbitrary facts about runtime inputs need not be proved. Required compile-time contexts, including bit widths, still need valid results. Runtime-dependent failures still report errors; error handling, further analysis, and constant-evaluation resource limits remain open.

Division D05y · z · ab · ac · ad · ae · af · ag · ah · ai · aj

The / operator between integers means fractional division: 5 / 2 has value 2.5. The result uses a numeric type capable of fractions even when the quotient is whole. The result type is float: fixed-precision binary64 (64-bit binary floating-point). Unlike growing int, its precision and range are fixed. One third is approximated, trading exact fractions for everyday speed and bounded result storage. For a nonzero divisor, round the mathematical quotient once to binary64; do not first approximate each integer operand. A huge nonzero integer divided by itself yields 1 even beyond binary64’s finite operand range, subject to resources. Ordinary finite results round to the nearest binary64 value; exact halfway cases choose the even significand (final stored binary digit zero). If the binary64 quotient overflows, report an overflow error instead of signed infinity; this checks the result, not operand size. Constant integer division result overflow also fails compilation wherever written, including unreachable code, when evaluable from integer literals, named compile-time integer constants, and built-in integer arithmetic/conversions. For tiny quotients, extend nearest, ties-to-even rounding through subnormal binary64 values down to zero, without an underflow error. Sufficiently tiny nonzero quotients may round to zero. Zero results preserve the division sign: tiny negative quotients yield negative zero. Treat integer zero as positive when determining the sign, so 0 / -5 gives negative zero and 0 / 5 gives positive zero. The precise upper rounding boundary, printing/comparisons, and other special-value rules remain open. With integer operands, a zero divisor reports an error: both 5 / 0 and 0 / 0 fail instead of producing infinity or NaN. When evaluable from integer literals, named compile-time integer constants, and built-in integer arithmetic/conversions, integer division by zero fails compilation wherever written, including unreachable code. Arbitrary facts about runtime inputs need not be proved; runtime-dependent zero divisors still report errors. Error handling and constant-evaluation details/resources remain open. Integer quotient and its spelling, negative quotient rounding, operand compatibility, contextual typing, conversions, and resource-exhaustion behavior remain open.

Exact fractions D05aa · ak · al · am · an · ao · ap · aq · ar · as · at

The standard library will provide an exact rational-number type named rational alongside binary64 integer division. Construct it as rational(numerator, denominator), numerator first and denominator second: rational(3, 4) creates three quarters. This short spelling uses the type-call shape already chosen for integer conversions. Both arguments must be integers: a float argument is a compile-time type error even when its value is a whole number. Each already-typed argument independently accepts growing int, signed fixed-width, or unsigned fixed-width integers; their types need not match. Preserve both exact values without narrowing or wrapping before construction. This avoids implicit float-to-integer conversion; any explicit float conversion facility remains to be chosen. Numerator and denominator sizes grow automatically like int, without a fixed bit-width ceiling, subject to available resources. This permits very large exact fractions with increasing memory and arithmetic cost. Adding two rational values with + computes their exact sum and returns a rational, subject to resources, with no float rounding: one third plus one sixth gives one half. Mixed operands and other rational operators remain open. Fractions reduce automatically to lowest terms: six eighths becomes three quarters without a manual simplification request. Reduced denominators are always positive, with any negative sign in the numerator. Zero has fraction form 0 over 1. Creating a rational with denominator zero reports an error for either a zero or nonzero numerator, rather than producing infinity or NaN. A denominator evaluable as zero from integer literals, named compile-time integer constants, and built-in integer arithmetic/conversions fails compilation wherever written, including unreachable ordinary code; arbitrary facts about runtime inputs need not be proved. This catches constant mistakes early but requires valid constant constructions in unused branches. Its module/import path, availability, literal contextual typing, named arguments/defaults, other construction forms, detailed constant-evaluation rules/resources, remaining arithmetic API beyond rational addition, intermediate arithmetic, conversions, physical storage/internal reduction strategy, resource-exhaustion policy, and error handling remain open. Integer / still returns float.

04 / Open questions

The rest stays open until chosen.

No Python or Go behavior fills these gaps automatically. The design documents record each accepted choice and track the unresolved areas.

Syntax & typing

Grammar, declarations, operators, detailed inference, conversions, nullability, generics, and any dynamic features.

Objects & numbers

Construction, overrides, interface compatibility, field initialization and defaults, overflow error handling, remaining conversions, fractional division’s remaining rules, integer quotient, and modulo.

Runtime & targets

Garbage collector behavior, native platforms, compilation model, WebAssembly host integration, and resource cleanup.

Programs & tools

Control flow, errors, modules, concurrency, standard library, build workflow, interoperability, and language evolution.