Operators and Precedence
Typhon’s operators are Python’s, plus three:
|>— pipe, for left-to-right composition.?— nullable type suffix, and Result-propagation expression suffix (context-dependent).as!— the checked boundary cast:EXPR as! TYPE(see below).
Precedence table
From tightest to loosest binding. Within a row, operators are left-associative unless noted otherwise.
| Precedence | Operators | Type | Notes |
|---|---|---|---|
| 1 (tightest) | f(...), x[...], x.attr | call, subscript, attribute | |
| 2 | ** | exponent | right-associative |
| 3 | +x, -x, ~x | unary | |
| 4 | *, @, /, //, % | multiplicative | |
| 5 | +, - | additive | |
| 6 | <<, >> | bitwise shift | |
| 7 | & | bitwise AND | |
| 8 | ^ | bitwise XOR | |
| 9 | | | bitwise OR | |
| 10 | |> | pipe (Typhon-specific) | |
| 11 | in, not in, is, is not, <, <=, >, >=, !=, == | comparison | chainable: 0 < x < 10 |
| 12 | not x | boolean NOT | |
| 13 | and | boolean AND | short-circuit |
| 14 | or | boolean OR | short-circuit |
| 15 | a if c else b | conditional | |
| 16 | lambda | anonymous fn | |
| 17 | := | walrus | |
| 18 (loosest) | assignment, yield, return | statements |
The ? suffix on a Result-typed expression binds at the same precedence as f(...) (call) — it’s part of the postfix chain.
Pipe precedence in detail
A |> chain must end its syntactic slot: arithmetic may feed into the pipe
on the left, but no operator may follow the chain — comparisons, boolean ops,
arithmetic tails and ternaries after a pipe are all parse errors. Parenthesise
the chain to use its value further:
x + 1 |> f() # ✅ == f(x + 1): arithmetic feeds the pipex |> f() |> g() # ✅ == g(f(x)): chains nest left(x |> f()) == 0 # ✅ parenthesised: compare the piped valuex |> f() == 0 # ❌ parse error — parenthesise as abovex |> f() and g(x) # ❌ parse error — parenthesise as aboveWhen in doubt, parenthesise.
Augmented assignments
x += 1x -= 1x *= 2x /= 2.0x //= 2x %= 2x **= 2x <<= 1x >>= 1x &= 0xFFx |= 0x01x ^= 0xAAAll require the target to be mut. The checker enforces.
The as! checked cast
EXPR as! TYPE is the checked boundary cast. Unlike a normal infix operator it does not slot into the precedence table — it is rewritten structurally rather than parsed by precedence:
- Left operand — the whole current syntactic slot: everything back to the enclosing bracket, a top-level
,/;/:separator, an assignment / augmented / walrus=, areturn/yield/if/while/assertkeyword, or the line start. Soa + b as! intcastsa + b, andsave(row[0] as! int, label)casts onlyrow[0]. - Right operand — a type expression: a dotted name, an optional
[...]subscript, and|-unions. Trailing code after the type stays outside the cast —x as! int + 1castsxthen adds1, and theforof a comprehension ([x as! int for x in xs]) stays outside.
An as! whose right side isn’t a type expression is left for the parser to reject cleanly. It lowers to __typhon_checked_cast__(EXPR, TYPE), a recursively-checked runtime cast. See as!.
Type-only operators
T?— nullable suffix on a type expression (only in annotation position).T | U— union (annotation position);T or Uis not valid in annotations.
Walrus (:=)
Standard Python:
if (n := len(xs)) > 10: print(f"{n} is too many")The walrus binds — and the binding is implicitly let (no mut form supported).
Common gotchas
is vs ==
Same as Python: is is identity, == is equality. Use is None / is not None for nullability checks (the checker only narrows on is).
or for nullable defaults
let name: str = maybe_name or "anonymous"or collapses falsy values (including None) to the right side. Common idiom for narrowing T? to T when “empty” is treated like “missing”.
Pipe and method calls
"hello" |> str.upper() # str.upper("hello") → "HELLO""hello".upper() # also "HELLO"Both work. Use whichever reads more naturally.
Where next
- Pipes (
|>) — the dedicated reference. - Nullable Types —
T?reference. - The ? Operator — Result propagation.