Scope and ownership
LinkUse this chapter to inspect error propagation and locate incompatible operand or return types.
This page owns postfix Result propagation and its function and failure boundaries. Types owns general type compatibility. Pattern matching owns case syntax. Blocks and control flow owns explicit returns and cleanup ordering. Functions and generics owns return annotations and inference. Diagnostic entries own code-specific explanations; command entries own process results.
Propagation syntax
LinkPropagation = PostfixExpression "?" ;
Expression precedence is specified by Expressions and operators.
Source evidence
- tryPostfix and parsePostfix at
af88afbc1d99· 1376-1433
Postfix operand
Postfix ? takes the preceding postfix expression as its operand.
Why
Postfix placement keeps the operation that may fail next to the point that forwards its error. It follows the binding order of calls and member access.
Source evidence
- tryPostfix and parsePostfix at
af88afbc1d99· 1376-1433
Values and early return
LinkOperand evaluation
In a named function, ? evaluates its Result operand once.
Why
Repeated evaluation could repeat input, output, or mutation before deciding whether to return an error.
Source evidence
- emitTry at
af88afbc1d99· 1927-1933
Successful operand
When the operand produces Ok(value), ? yields value and evaluation continues in the enclosing named function.
Why
A successful operation supplies the value needed by later expressions without ending the function.
Error return
When the operand produces Err(error), ? returns that Result with its error payload from the enclosing named function. Subsequent expressions in its body are skipped.
Why
Preserving the error payload keeps failure information intact. Skipping the remaining body prevents later work from using a value that was not produced.
Operand fault
A runtime fault raised while evaluating the operand continues as a runtime fault.
An ordinary runtime fault follows fault termination.
Why
A runtime fault interrupts evaluation, while an Err is an ordinary value. Keeping these paths distinct prevents propagation from silently converting a fault into a Result.
Operand and return types
LinkSuccess type
For an operand of type Result<T, E>, operand? has type T.
Why
Successful propagation supplies the payload to the next expression. Its type is the success type because the Result wrapper has been handled.
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166
Result operand requirement
A propagation operand with a resolved non-Result type, including Option<T>, is rejected during checking with TPZ5001.
Why
Result identifies both a success value and an error to return. Option supplies no error payload for the same propagation rule.
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166
Enclosing function
Using ? without an enclosing function return context is rejected during checking with TPZ5001.
Why
Propagation needs a function return target. Without one, the error has no destination.
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166
Result return requirement
Propagating a Result from a function with a resolved non-Result return type is rejected during checking with TPZ5001.
An undetermined Result error type follows the annotation requirement.
Why
The enclosing return type must admit the error Result that propagation returns. Otherwise the function could produce a value outside its declared type.
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166
Error type compatibility
For an operand of type Result<T, E> in a function returning Result<U, F>, E must be assignable to F. Incompatible resolved error types produce TPZ5001 during checking. The propagation step does not compare T with U.
Why
An error can leave the function at this point, so it must fit the declared error type. The success payload remains local and can be transformed before return.
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166 - reportExpectedType at
af88afbc1d99· 1660-1684
Annotating an unresolved Result error type
If the Result error type cannot be determined, checking rejects the expression with TPZ5022 and requires a type annotation. Inference does not substitute a broad fallback type. This is the same diagnostic as the return annotation requirement.
This uses the same diagnostic as return annotation requirements. Existing propagation context rules determine where propagation is admitted. This decision does not add a propagation target for nested lambdas or callbacks.
Why
Return inference and Result error inference use the same annotation rule and diagnostic. The program therefore has a determined error type before emission.
Source evidence
- issues/31 at
1cbf38d3d422
Examples and counterexamples
LinkThe checking examples pair rejected files with complete repairs. The execution example makes operand evaluation and continuation visible.
Option operand
Linkfunction advance(value: Option<int>) -> Result<int, string> {let number: int = value?Ok(number + 1)}
No stdout.
error[TPZ5001]: `?` requires a Result value, found `Option<int>` --> example.tpz:2:23 | 2 | let number: int = value? | ^^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001result-required-for-propagation atexample.tpz:2:23
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5001]: `?` requires a Result value, found `Option<int>`--> example.tpz:2:23|2 | let number: int = value?| ^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · checking TPZ5001; cause result-required-for-propagation; primary location asserted; additional diagnostics allow
- Passed · stdout matches the authored expectation
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166 - reportExpectedType at
af88afbc1d99· 1660-1684
Result operand
Linkfunction advance(value: Result<int, string>) -> Result<int, string> {let number: int = value?Ok(number + 1)}let success: Result<int, string> = advance(Ok(4))let failure: Result<int, string> = advance(Err("missing"))
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Observed assertions · passed · 5.21.3
Exit code 0.
No stdout.
No stderr.
- Passed · expected 0; observed 0
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166 - reportExpectedType at
af88afbc1d99· 1660-1684
Integer return type
Linkfunction read(value: Result<int, string>) -> int {value?}
No stdout.
error[TPZ5001]: `?` propagates an error, but the function returns `int`, not a Result --> example.tpz:2:5 | 2 | value? | ^^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001propagation-requires-result-return atexample.tpz:2:5
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5001]: `?` propagates an error, but the function returns `int`, not a Result--> example.tpz:2:5|2 | value?| ^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · checking TPZ5001; cause propagation-requires-result-return; primary location asserted; additional diagnostics allow
- Passed · stdout matches the authored expectation
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166 - reportExpectedType at
af88afbc1d99· 1660-1684
Result return type
Linkfunction read(value: Result<int, string>) -> Result<int, string> {let number: int = value?Ok(number)}
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Observed assertions · passed · 5.21.3
Exit code 0.
No stdout.
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166 - reportExpectedType at
af88afbc1d99· 1660-1684
Incompatible error types
Linkfunction relay(value: Result<int, string>) -> Result<int, int> {let number: int = value?Ok(number)}
No stdout.
error[TPZ5001]: expected `int`, found `string` --> example.tpz:2:23 | 2 | let number: int = value? | ^^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001propagated-error-type-not-assignable atexample.tpz:2:23
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5001]: expected `int`, found `string`--> example.tpz:2:23|2 | let number: int = value?| ^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · checking TPZ5001; cause propagated-error-type-not-assignable; primary location asserted; additional diagnostics allow
- Passed · stdout matches the authored expectation
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166 - reportExpectedType at
af88afbc1d99· 1660-1684
Matching error types
Linkfunction relay(value: Result<int, string>) -> Result<int, string> {let number: int = value?Ok(number)}
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Observed assertions · passed · 5.21.3
Exit code 0.
No stdout.
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- inferTryExpression at
af88afbc1d99· 1123-1166 - reportExpectedType at
af88afbc1d99· 1660-1684
Propagation at top level
Linklet value: Result<int, string> = Ok(1)let number: int = value?
No stdout.
error[TPZ5001]: `return` outside a function --> example.tpz:2:19 | 2 | let number: int = value? | ^^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001propagation-outside-function atexample.tpz:2:19
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5001]: `return` outside a function--> example.tpz:2:19|2 | let number: int = value?| ^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · checking TPZ5001; cause propagation-outside-function; primary location asserted; additional diagnostics allow
- Passed · stdout matches the authored expectation
Source evidence
- tryPostfix and parsePostfix at
af88afbc1d99· 1376-1433 - inferTryExpression at
af88afbc1d99· 1123-1166
Observe success and early error return
Linkimport std.iofunction observe(value: Result<int, string>) -> Result<int, string> {io.writeLine("operand")value}function advance(value: Result<int, string>) -> Result<int, string> {let number: int = observe(value)?io.writeLine("continued")Ok(number + 1)}let success: Result<int, string> = advance(Ok(4))match (success) {case Ok(number) => io.writeLine("ok {number}")case Err(error) => io.writeLine("unexpected {error}")}let failure: Result<int, string> = advance(Err("missing"))match (failure) {case Ok(number) => io.writeLine("unexpected {number}")case Err(error) => io.writeLine("error {error}")}
operand continued ok 5 operand error missing
Expected result
Expected result
Execution completes. Exit code 0.
operandcontinuedok 5operanderror missing
Observed assertions · passed · 5.21.3
Exit code 0.
operandcontinuedok 5operanderror missing
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stdout matches the authored expectation
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- emitTry at
af88afbc1d99· 1927-1933 - try_value at
af88afbc1d99· 40-61 - inferTryExpression at
af88afbc1d99· 1123-1166
An unknown Result error type requires an annotation
Linkfunction value() { Ok(1) }let result = value()
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022result-error-annotation-required atmain.tpz:1:10
Source evidence
- issues/31 at
1cbf38d3d422
A Result return annotation fixes the error type
Linkimport std.iofunction value() -> Result<int, string> { Ok(1) }let result = match (value()) {case Ok(number) => numbercase Err(_) => 0}io.writeLine("{result}")
1
Expected result
Expected result
Execution completes. Exit code 0.
1
Source evidence
- issues/31 at
1cbf38d3d422