Logical Operators¶
| Operator | Description | C# Mapping |
|---|---|---|
and |
Logical AND (short-circuit) | && |
or |
Logical OR (short-circuit) | \|\| |
not |
Logical NOT | ! |
Truthiness¶
Operands of and, or, and not are evaluated for truthiness — the same rule that governs if, while, assert, ternary conditions, comprehension filters, and match guards (an arm guard case p if g: and a per-alternative guard pattern (p if g) alike). A type is truth-testable when it has a falsy case:
| Type | Truthiness lowering | Falsy value |
|---|---|---|
bool |
identity | False |
int |
x != 0 |
0 |
float |
x != 0.0 |
0.0 |
long |
x != 0L |
0L |
str |
x.Length > 0 |
"" |
bytes |
((ISized)x).Count > 0 |
b"" |
collections (list, dict, set, and any type whose CLR shape carries ISized — the collections mappings defaultdict, OrderedDict, ChainMap, Counter, the dict views) |
((ISized)x).Count > 0 |
empty |
None |
false (always) |
None |
T? (strict Optional) |
x.IsSome |
None() |
T \| None (loose nullable) |
x != null |
None |
UDT with __bool__ |
x.IsTrue |
implementation-defined |
UDT with __len__ |
((ISized)x).Count > 0 |
empty |
conditional (x if c else y) |
distributed: c ? truth(x) : truth(y) |
per-branch |
fixed-arity tuple[T1, ..., Tn] |
refused (SPY0220) | no falsy case — every value of a given tuple type has the same, statically-known truthiness |
| objects, functions, delegates | refused (SPY0220) | no falsy case |
Deviation from Python: Python makes objects without __bool__/__len__ vacuously truthy. Sharpy refuses them — the check can never do anything useful.
Deviation from Python (tuple truthiness): every non-empty Python tuple is truthy and () is falsy — bool()/len()-based, like any other sequence. A fixed-arity tuple[T1, ..., Tn] in Sharpy is refused instead: its arity is part of its TYPE, so every value of that type has the SAME truthiness (always truthy for a non-empty arity, always falsy for the zero-arity tuple[] the () literal spells) — testing it is never a runtime question, only a compile-time constant. Test len(t) or a specific element instead. A tuple reached through a loose tuple[...] | None or a strict tuple[...]? wrapper is unaffected — the wrapper's own truthiness (null-check / is-some) is tested, never the tuple's.
The refusal is not uniform across every use of the word "truthiness": it covers the sixteen
truth-testing positions — if, while, assert, and/or/not, ternary conditions,
comprehension filters, match guards and the rest of the list above — where a tuple operand is a
compile-time constant masquerading as a runtime test. An explicit bool(t) call is a
conversion, not a truth-testing position, and is admitted, as in Python:
def main() -> None:
t = (1, "a")
print(bool(t)) # admitted — bool() is not a truth-testing position
print(len(t)) # 2 — the arity, the thing worth testing
print(bool(())) # False — the zero-arity tuple is falsy, like any empty sequence
Output:
bool(t) answers from the arity, so it is False for the zero-arity tuple[] that () spells and
True for every other arity, matching Python.
A conditional expression in a truthiness position distributes the test per branch
(TruthinessLowering.Distributed). Each branch is tested for truthiness independently, so the
branches need not share a common type. Distribution recurses: a conditional nested inside a
distributed branch is itself in a truthiness position and needs no type either.
class Yes:
def __bool__(self) -> bool:
return False
class No:
def __bool__(self) -> bool:
return True
def main() -> None:
flag = True
g = False
if Yes() if flag else No(): # Yes and No share no common type
print("truthy")
else:
print("falsy")
if ("a" if g else 0) if flag else 1: # the nested ternary distributes too
print("nested-truthy")
else:
print("nested-falsy")
Output:
Return Type¶
and and or return bool, not the operand value. This is a deliberate deviation from Python, where "" or "Anonymous" returns "Anonymous". In Sharpy, "" or "Anonymous" evaluates to True. See null_coalescing_operator.md for the ?? operator that provides the Python-style fallback behavior.
Assigning the result of or/and to a non-bool slot is refused with a steer naming the rewrite:
name: str | None = None
fallback: str = name or "Anonymous" # error: Cannot assign type 'bool' ... use '??'
For nullable or optional left operands, ?? is the Python-style fallback: