Result Errors
tyc::invalid_question_op
? is rejected in two situations (the v0.9.0 help text covers both explicitly):
1. Outside a Result-returning function
def f() -> int: let n: int = parse_port("8080")? # ❌ — function returns int, not Result return nFix: change the signature, or match explicitly.
def f() -> Result[int, str]: let n: int = parse_port("8080")? return Ok(n)2. Inside a comprehension
Comprehensions lower to nested for-loops in Python, so the surrounding function frame ? would short-circuit out of is not the comprehension’s frame:
def collect(xs: list[str]) -> Result[list[int], str]: return Ok([parse(x)? for x in xs]) # ❌ — `?` inside a comprehensionFix: pre-extract with an explicit loop, or chain with .and_then / .map:
def collect(xs: list[str]) -> Result[list[int], str]: mut out: list[int] = [] for x in xs: out.append(parse(x)?) return Ok(out)tyc::result_error_mismatch
? short-circuits an Err[E1] to a function returning Result[T, E2] where E1 is not assignable to E2:
def parse_str() -> Result[int, str]: ...
def f() -> Result[int, AppError]: let n: int = parse_str()? # ❌ — str not assignable to AppError return Ok(n)Fix: convert at the boundary with .map_err:
def f() -> Result[int, AppError]: let n: int = parse_str().map_err(lambda msg: AppError(message=msg))? return Ok(n)Or unfold the bridge with match:
def f() -> Result[int, AppError]: match parse_str(): case Ok(n): return Ok(n) case Err(msg): return Err(AppError(message=msg))Since v0.8.0 the same check fires inside with-chain ?-bindings — every binding’s implicit propagation path is validated against the enclosing function’s declared error type. Since v0.9.0 the explicit else err: return Err(err) form goes through the same check too; previously the validation was gated on the synthetic ?-op temp shape, so an else block could silently return an Err whose payload type didn’t match the function’s declared return.
Closed Result method surface (v0.13.0)
The Result / Ok / Err method surface is now closed. Calling an unknown method on a Result value — a typo like .unwarp() — fires tyc::attribute_not_found at check time. Before v0.13.0 an unknown method passed the checker and crashed at runtime with AttributeError.
def f() -> Result[int, str]: let r: Result[int, str] = parse_port("8080") return Ok(r.unwarp()) # ❌ tyc::attribute_not_found — typo for `.unwrap`The known method set is:
- Combinators:
.map/.map_err/.and_then/.or_else. - Unwrap / query family:
.unwrap()/.expect(msg)/.unwrap_or(d)/.unwrap_or_else(f)/.ok()/.err()/.is_ok()/.is_err().
Fix: correct the method name, or match on the Result if you need a shape these methods don’t cover.
tyc::result_unused (reserved)
Future-reserved diagnostic for a Result-returning call whose value is discarded:
def save() -> Result[None, str]: ...
save() # would warn: Result not consumedNot yet emitted by the compiler. Today, an ignored Result is silently dropped; the diagnostic name is reserved so the eventual rule has a stable code from day one. Use ? or match to consume Result values today.
Where next
Result, Ok, Errreference — the type.- The ? Operator reference — every rule.
- Error Handling (tour) — teaching page.