Skip to content
Topaz6.0 · Before release
Download

Search documentation

Specification 6.0 · Not released · Tested product 5.21.3

Use arrow keys to choose a result. Press Enter to open.

Type an API name, diagnostic number, rule, or project topic.

    Language · chapter 12 · 11 rules

    Failure and propagation

    Read and check functions that forward Result errors with postfix ?.

    Scope and ownership

    Link

    Use 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

    Link
    EBNF
    1. Propagation = PostfixExpression "?" ;

    Expression precedence is specified by Expressions and operators.

    Source evidence
    Ruletopaz.rule.failure-and-propagation.postfix

    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

    Values and early return

    Link
    Ruletopaz.rule.failure-and-propagation.evaluate-once

    Operand 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
    Ruletopaz.rule.failure-and-propagation.ok

    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.

    Source evidence
    • emitTry at af88afbc1d99 · 1927-1933
    • try_value at af88afbc1d99 · 40-61
    Ruletopaz.rule.failure-and-propagation.err

    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.

    Source evidence
    • emitTry at af88afbc1d99 · 1927-1933
    • try_value at af88afbc1d99 · 40-61
    Ruletopaz.rule.failure-and-propagation.operand-fault

    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.

    Source evidence
    • emitTry at af88afbc1d99 · 1927-1933
    • try_value at af88afbc1d99 · 40-61

    Operand and return types

    Link
    Ruletopaz.rule.failure-and-propagation.result-type

    Success 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

    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

    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
    Ruletopaz.rule.failure-and-propagation.result-return

    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

    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

    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

    Examples and counterexamples

    Link

    The checking examples pair rejected files with complete repairs. The execution example makes operand evaluation and continuation visible.

    Option operand

    Link
    example.tpzOption operandOpen in Playground
    1. function advance(value: Option<int>) -> Result<int, string> {
    2. let number: int = value?
    3. Ok(number + 1)
    4. }
    Recorded outputexit 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
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 result-required-for-propagation at example.tpz:2:23
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5001]: `?` requires a Result value, found `Option<int>`
    2. --> example.tpz:2:23
    3. |
    4. 2 | let number: int = value?
    5. | ^^^^^^
    6. 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

    Result operand

    Link
    example.tpzResult operandOpen in Playground
    1. function advance(value: Result<int, string>) -> Result<int, string> {
    2. let number: int = value?
    3. Ok(number + 1)
    4. }
    5. let success: Result<int, string> = advance(Ok(4))
    6. let failure: Result<int, string> = advance(Err("missing"))
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    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

    Integer return type

    Link
    example.tpzInteger return typeOpen in Playground
    1. function read(value: Result<int, string>) -> int {
    2. value?
    3. }
    Recorded outputexit 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
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 propagation-requires-result-return at example.tpz:2:5
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5001]: `?` propagates an error, but the function returns `int`, not a Result
    2. --> example.tpz:2:5
    3. |
    4. 2 | value?
    5. | ^^^^^^
    6. 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

    Result return type

    Link
    example.tpzResult return typeOpen in Playground
    1. function read(value: Result<int, string>) -> Result<int, string> {
    2. let number: int = value?
    3. Ok(number)
    4. }
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    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

    Incompatible error types

    Link
    example.tpzIncompatible error typesOpen in Playground
    1. function relay(value: Result<int, string>) -> Result<int, int> {
    2. let number: int = value?
    3. Ok(number)
    4. }
    Recorded outputexit 1
    No stdout.
    error[TPZ5001]: expected `int`, found `string`
     --> example.tpz:2:23
      |
    2 |     let number: int = value?
      |                       ^^^^^^
    
    example.tpz: 1 type diagnostic
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 propagated-error-type-not-assignable at example.tpz:2:23
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5001]: expected `int`, found `string`
    2. --> example.tpz:2:23
    3. |
    4. 2 | let number: int = value?
    5. | ^^^^^^
    6. 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

    Matching error types

    Link
    example.tpzMatching error typesOpen in Playground
    1. function relay(value: Result<int, string>) -> Result<int, string> {
    2. let number: int = value?
    3. Ok(number)
    4. }
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    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

    Propagation at top level

    Link
    example.tpzPropagation at top levelOpen in Playground
    1. let value: Result<int, string> = Ok(1)
    2. let number: int = value?
    Recorded outputexit 1
    No stdout.
    error[TPZ5001]: `return` outside a function
     --> example.tpz:2:19
      |
    2 | let number: int = value?
      |                   ^^^^^^
    
    example.tpz: 1 type diagnostic
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 propagation-outside-function at example.tpz:2:19
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5001]: `return` outside a function
    2. --> example.tpz:2:19
    3. |
    4. 2 | let number: int = value?
    5. | ^^^^^^
    6. 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

    Observe success and early error return

    Link
    example.tpzObserve success and early error returnOpen in Playground
    1. import std.io
    2. function observe(value: Result<int, string>) -> Result<int, string> {
    3. io.writeLine("operand")
    4. value
    5. }
    6. function advance(value: Result<int, string>) -> Result<int, string> {
    7. let number: int = observe(value)?
    8. io.writeLine("continued")
    9. Ok(number + 1)
    10. }
    11. let success: Result<int, string> = advance(Ok(4))
    12. match (success) {
    13. case Ok(number) => io.writeLine("ok {number}")
    14. case Err(error) => io.writeLine("unexpected {error}")
    15. }
    16. let failure: Result<int, string> = advance(Err("missing"))
    17. match (failure) {
    18. case Ok(number) => io.writeLine("unexpected {number}")
    19. case Err(error) => io.writeLine("error {error}")
    20. }
    Recorded outputexit 0
    operand
    continued
    ok 5
    operand
    error missing
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. operand
    2. continued
    3. ok 5
    4. operand
    5. error missing
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. operand
    2. continued
    3. ok 5
    4. operand
    5. error 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

    Link
    main.tpzAn unknown Result error type requires an annotationOpen in Playground
    1. function value() { Ok(1) }
    2. let result = value()
    Expected outputNonzero exit
    No stdout.
    $ topaz check main.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5022 result-error-annotation-required at main.tpz:1:10
    Source evidence

    A Result return annotation fixes the error type

    Link
    main.tpzA Result return annotation fixes the error typeOpen in Playground
    1. import std.io
    2. function value() -> Result<int, string> { Ok(1) }
    3. let result = match (value()) {
    4. case Ok(number) => number
    5. case Err(_) => 0
    6. }
    7. io.writeLine("{result}")
    Expected outputexit 0
    1
    
    $ topaz run main.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1
    Source evidence

    Pending contracts

    Link

    Related references

    Link

    TPZ5001

    Functions and generics

    check

    TPZ5022

    Menu

    Download