Skip to content

Dialect rules (generated)

This page lists every typing rule the compiler will apply, dumped from the rule table itself (situ.compiler.lang.rules). Generated page. Run situ rules to regenerate it; make lint fails if it is stale.

Each row is one rule: at these operand types, the construct has this result type and lowers to this JavaScript. The last column is why that JavaScript means what Python means. A combination that appears nowhere below has no rule, so no lowering; the compiler rejects it with a CompileError. That is the dialect's enforcement-by-omission: a missing entry is the rejection.

$a / $b are the operand placeholders. For the prose version (what to write, and why the dialect is shaped this way) see The dialect.

Binary operators

Construct Left Right Result Lowers to Why it is faithful
a + b bool bool int ($a + $b) JS + on two numbers is numeric addition
a + b bool int int ($a + $b) JS + on two numbers is numeric addition
a + b bool float float ($a + $b) JS + on two numbers is numeric addition
a + b int bool int ($a + $b) JS + on two numbers is numeric addition
a + b int int int ($a + $b) JS + on two numbers is numeric addition
a + b int float float ($a + $b) JS + on two numbers is numeric addition
a + b float bool float ($a + $b) JS + on two numbers is numeric addition
a + b float int float ($a + $b) JS + on two numbers is numeric addition
a + b float float float ($a + $b) JS + on two numbers is numeric addition
a + b str str str ($a + $b) JS + on two strings is concatenation
a + b list[int] list[int] list[int] [...$a, ...$b] JS + on arrays coerces to string; a spread concatenates
a + b list[str] list[str] list[str] [...$a, ...$b] JS + on arrays coerces to string; a spread concatenates
a - b bool bool int ($a - $b) identical
a - b bool int int ($a - $b) identical
a - b bool float float ($a - $b) identical
a - b int bool int ($a - $b) identical
a - b int int int ($a - $b) identical
a - b int float float ($a - $b) identical
a - b float bool float ($a - $b) identical
a - b float int float ($a - $b) identical
a - b float float float ($a - $b) identical
a - b set[int] set[int] set[int] S.sdiff($a, $b) Python set difference/union/intersection/symmetric-difference. JS Set has no operators at all, and its .has() uses SameValueZero (so it would miss that Python holds True and 1 as one member) -- hence a helper over S.eq.
a * b bool bool int ($a * $b) identical
a * b bool int int ($a * $b) identical
a * b bool float float ($a * $b) identical
a * b bool str str ($b).repeat(Math.max(0, $a)) Python 'ab' * 3 repeats (JS * on a string is NaN). The count must be an INT -- 'ab' * 2.5 is a TypeError in Python, and String.repeat would happily truncate it. A negative count gives '' in Python where repeat() throws, hence the clamp. Commutative: 3 * 'ab'.
a * b int bool int ($a * $b) identical
a * b int int int ($a * $b) identical
a * b int float float ($a * $b) identical
a * b int str str ($b).repeat(Math.max(0, $a)) Python 'ab' * 3 repeats (JS * on a string is NaN). The count must be an INT -- 'ab' * 2.5 is a TypeError in Python, and String.repeat would happily truncate it. A negative count gives '' in Python where repeat() throws, hence the clamp. Commutative: 3 * 'ab'.
a * b float bool float ($a * $b) identical
a * b float int float ($a * $b) identical
a * b float float float ($a * $b) identical
a * b str bool str ($a).repeat(Math.max(0, $b)) Python 'ab' * 3 repeats (JS * on a string is NaN). The count must be an INT -- 'ab' * 2.5 is a TypeError in Python, and String.repeat would happily truncate it. A negative count gives '' in Python where repeat() throws, hence the clamp.
a * b str int str ($a).repeat(Math.max(0, $b)) Python 'ab' * 3 repeats (JS * on a string is NaN). The count must be an INT -- 'ab' * 2.5 is a TypeError in Python, and String.repeat would happily truncate it. A negative count gives '' in Python where repeat() throws, hence the clamp.
a / b bool bool float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a / b bool int float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a / b bool float float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a / b int bool float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a / b int int float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a / b int float float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a / b float bool float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a / b float int float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a / b float float float S.div($a, $b) Python / is true division: it ALWAYS yields a float, even for 4 / 2 (hence the FLOAT result type: str(4/2) is '2.0'). It also RAISES on a zero divisor where JS yields Infinity with no error, so it cannot be a bare /. S.div throws, as Python does.
a // b bool bool int S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a // b bool int int S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a // b bool float float S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a // b int bool int S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a // b int int int S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a // b int float float S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a // b float bool float S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a // b float int float S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a // b float float float S.floordiv($a, $b) Python // floors toward -inf (-7 // 2 == -4, where JS trunc gives -3), and raises on a zero divisor. It keeps the operands' kind: int // int is int, 7.0 // 2 is a float.
a % b bool bool int S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a % b bool int int S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a % b bool float float S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a % b int bool int S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a % b int int int S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a % b int float float S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a % b float bool float S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a % b float int float S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a % b float float float S.mod($a, $b) Python % takes the sign of the DIVISOR (floor-mod): -1 % 3 is 2 in Python and -1 in JS. It also raises on a zero divisor. This is why % was removed from the untyped emitter -- with types it comes back, correct.
a | b set[int] set[int] set[int] S.sunion($a, $b) Python set difference/union/intersection/symmetric-difference. JS Set has no operators at all, and its .has() uses SameValueZero (so it would miss that Python holds True and 1 as one member) -- hence a helper over S.eq.
a & b set[int] set[int] set[int] S.sinter($a, $b) Python set difference/union/intersection/symmetric-difference. JS Set has no operators at all, and its .has() uses SameValueZero (so it would miss that Python holds True and 1 as one member) -- hence a helper over S.eq.
a ^ b set[int] set[int] set[int] S.sxor($a, $b) Python set difference/union/intersection/symmetric-difference. JS Set has no operators at all, and its .has() uses SameValueZero (so it would miss that Python holds True and 1 as one member) -- hence a helper over S.eq.

Comparisons

Construct Left Right Result Lowers to Why it is faithful
a == b bool bool bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b bool int bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b bool float bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b int bool bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b int int bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b int float bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b float bool bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b float int bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b float float bool (Number($a) === Number($b)) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a == b str str bool ($a === $b) === on two strings is value equality in both languages
a == b none none bool ($a === $b) null === null
a == b list[int] list[int] bool S.eq($a, $b) Python compares lists/dicts/sets STRUCTURALLY; JS === is reference identity, so xs == [1, 2] was false in the browser and true in Python. S.eq is a deep compare.
a == b list[str] list[str] bool S.eq($a, $b) Python compares lists/dicts/sets STRUCTURALLY; JS === is reference identity, so xs == [1, 2] was false in the browser and true in Python. S.eq is a deep compare.
a == b set[int] set[int] bool S.eq($a, $b) Python compares lists/dicts/sets STRUCTURALLY; JS === is reference identity, so xs == [1, 2] was false in the browser and true in Python. S.eq is a deep compare.
a == b dict[str, int] dict[str, int] bool S.eq($a, $b) Python compares lists/dicts/sets STRUCTURALLY; JS === is reference identity, so xs == [1, 2] was false in the browser and true in Python. S.eq is a deep compare.
a != b bool bool bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b bool int bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b bool float bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b int bool bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b int int bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b int float bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b float bool bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b float int bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b float float bool (!(Number($a) === Number($b))) a Python bool IS an int (True == 1), but JS true === 1 is false. Number() on both sides restores Python's answer.
a != b str str bool (!($a === $b)) === on two strings is value equality in both languages
a != b none none bool (!($a === $b)) null === null
a != b list[int] list[int] bool (!S.eq($a, $b)) Python compares lists/dicts/sets STRUCTURALLY; JS === is reference identity, so xs == [1, 2] was false in the browser and true in Python. S.eq is a deep compare.
a != b list[str] list[str] bool (!S.eq($a, $b)) Python compares lists/dicts/sets STRUCTURALLY; JS === is reference identity, so xs == [1, 2] was false in the browser and true in Python. S.eq is a deep compare.
a != b set[int] set[int] bool (!S.eq($a, $b)) Python compares lists/dicts/sets STRUCTURALLY; JS === is reference identity, so xs == [1, 2] was false in the browser and true in Python. S.eq is a deep compare.
a != b dict[str, int] dict[str, int] bool (!S.eq($a, $b)) Python compares lists/dicts/sets STRUCTURALLY; JS === is reference identity, so xs == [1, 2] was false in the browser and true in Python. S.eq is a deep compare.
a < b bool bool bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b bool int bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b bool float bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b int bool bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b int int bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b int float bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b float bool bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b float int bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b float float bool (Number($a) < Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a < b str str bool ($a < $b) JS compares strings lexicographically, as Python does
a <= b bool bool bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b bool int bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b bool float bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b int bool bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b int int bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b int float bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b float bool bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b float int bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b float float bool (Number($a) <= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a <= b str str bool ($a <= $b) JS compares strings lexicographically, as Python does
a > b bool bool bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b bool int bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b bool float bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b int bool bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b int int bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b int float bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b float bool bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b float int bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b float float bool (Number($a) > Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a > b str str bool ($a > $b) JS compares strings lexicographically, as Python does
a >= b bool bool bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b bool int bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b bool float bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b int bool bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b int int bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b int float bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b float bool bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b float int bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b float float bool (Number($a) >= Number($b)) both numeric; Number() normalizes a bool (Python's True > 0 is True)
a >= b str str bool ($a >= $b) JS compares strings lexicographically, as Python does

Membership

Construct Left Right Result Lowers to Why it is faithful
a in b bool list[int] bool ($b).some((v) => Number(v) === Number($a)) .includes / Set.has use ===, so True in [1, 2] was false in JS and true in Python. Comparing through Number() restores Python's bool-is-an-int rule.
a in b bool set[int] bool [...$b].some((v) => Number(v) === Number($a)) .includes / Set.has use ===, so True in [1, 2] was false in JS and true in Python. Comparing through Number() restores Python's bool-is-an-int rule.
a in b int list[int] bool ($b).some((v) => Number(v) === Number($a)) .includes / Set.has use ===, so True in [1, 2] was false in JS and true in Python. Comparing through Number() restores Python's bool-is-an-int rule.
a in b int set[int] bool [...$b].some((v) => Number(v) === Number($a)) .includes / Set.has use ===, so True in [1, 2] was false in JS and true in Python. Comparing through Number() restores Python's bool-is-an-int rule.
a in b float list[int] bool ($b).some((v) => Number(v) === Number($a)) .includes / Set.has use ===, so True in [1, 2] was false in JS and true in Python. Comparing through Number() restores Python's bool-is-an-int rule.
a in b float set[int] bool [...$b].some((v) => Number(v) === Number($a)) .includes / Set.has use ===, so True in [1, 2] was false in JS and true in Python. Comparing through Number() restores Python's bool-is-an-int rule.
a in b str str bool ($b).includes($a) substring containment, as in Python
a in b str list[str] bool ($b).includes($a) same-typed non-numeric elements: === is value equality
a in b str dict[str, int] bool Object.hasOwn($b, $a) Python's in on a dict tests KEYS. .includes searches values, which is why the untyped emitter had to ban this outright; with types it is one call.

Unary operators

Construct Left Right Result Lowers to Why it is faithful
-x bool int (-$a) identical, but a bool operand yields an int as in Python
-x int int (-$a) identical, but a bool operand yields an int as in Python
-x float float (-$a) identical, but a bool operand yields an int as in Python
+x bool int (+$a) identical, but a bool operand yields an int as in Python
+x int int (+$a) identical, but a bool operand yields an int as in Python
+x float float (+$a) identical, but a bool operand yields an int as in Python
not x bool bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.
not x int bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.
not x float bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.
not x str bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.
not x none bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.
not x list[int] bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.
not x list[str] bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.
not x set[int] bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.
not x dict[str, int] bool (!S.truthy($a)) Python and JS disagree on truthiness: [] and {} are FALSY in Python and TRUTHY in JS, so not xs (xs == []) was True in Python and false in the browser. S.truthy implements Python's rule (0, '', [], {}, set(), None are falsy). Every not / and / or / ternary / :show condition goes through it.

Subscript

Construct Left Right Result Lowers to Why it is faithful
a[b] str int str S.idx($a, $b) Python indexes from the end for a negative index (xs[-1]) -- JS [-1] is undefined, which is why the untyped emitter had to ban it. S.idx uses .at() AND raises out of range, because Python raises IndexError where .at() would return undefined with no error. A lowering that swallows a Python exception is not a faithful lowering.
a[b] list[int] int int S.idx($a, $b) Python indexes from the end for a negative index (xs[-1]) -- JS [-1] is undefined, which is why the untyped emitter had to ban it. S.idx uses .at() AND raises out of range, because Python raises IndexError where .at() would return undefined with no error. A lowering that swallows a Python exception is not a faithful lowering.
a[b] list[str] int str S.idx($a, $b) Python indexes from the end for a negative index (xs[-1]) -- JS [-1] is undefined, which is why the untyped emitter had to ban it. S.idx uses .at() AND raises out of range, because Python raises IndexError where .at() would return undefined with no error. A lowering that swallows a Python exception is not a faithful lowering.
a[b] dict[str, int] str int S.key($a, $b) Python raises KeyError on a missing key; a bare JS property read returns undefined.

Whitelisted calls

Construct Left Right Result Lowers to Why it is faithful
len(str) int ($a).length same count; len() is always an int
len(list[int]) int ($a).length same count; len() is always an int
len(set[int]) int ($a).size a JS Set exposes .size, not .length
len(dict[str, int]) int Object.keys($a).length Python's len(dict) counts keys
abs(int) int Math.abs($a) identical
abs(float) float Math.abs($a) identical
round(float) int S.round($a) Python rounds HALF TO EVEN (round(2.5) == 2, round(3.5) == 4); JS Math.round rounds half up (2.5 -> 3). S.round implements banker's rounding.
round(float, int) num S.round($a, $b) as above; the precision must be an int
min(int, int) int (($a) <= ($b) ? ($a) : ($b)) Python's min/max return the OPERAND, keeping its type (min(True, 2) is True); Math.min would coerce it to a number. A ternary preserves the value itself.
max(float, float) float (($a) >= ($b) ? ($a) : ($b)) Python's min/max return the OPERAND, keeping its type (min(True, 2) is True); Math.min would coerce it to a number. A ternary preserves the value itself.
sum(list[int]) int ($a).reduce((x, y) => x + y, 0) Python's sum() starts at the int 0, as does this
str(int) str S.str($a) Python's str() of a list/dict/bool is not JS String()

String methods

Construct Left Right Result Lowers to Why it is faithful
s.endswith(…) str bool ($a).endsWith($b) identical (no position argument)
s.lower() str str ($a).toLowerCase() identical for ASCII; both use Unicode simple case
s.startswith(…) str bool ($a).startsWith($b) identical (no position argument)
s.strip() str str ($a).trim() Python's argument-less strip trims whitespace
s.upper() str str ($a).toUpperCase() as above

180 rules.