From 686a4ae0aa65814778676641ae890f006c3a1939 Mon Sep 17 00:00:00 2001 From: Heshan Padmasiri Date: Mon, 14 Oct 2024 09:16:52 +0530 Subject: [PATCH] Apply suggestions from code review Co-authored-by: Maryam Ziyad --- .../check-expression/check_expression.bal | 5 ++- examples/check-expression/check_expression.md | 2 +- .../check-expression/check_expression.out | 1 - examples/error-reporting/error_reporting.bal | 42 ++++++++++--------- examples/error-reporting/error_reporting.md | 13 +++--- examples/error-reporting/error_reporting.out | 16 +++---- examples/error-subtyping/error_subtyping.bal | 11 +++-- examples/error-subtyping/error_subtyping.md | 6 ++- .../error_type_intersection.bal | 2 +- examples/trap-expression/trap_expression.bal | 9 ++-- examples/trap-expression/trap_expression.md | 2 +- examples/trap-expression/trap_expression.out | 5 ++- 12 files changed, 64 insertions(+), 50 deletions(-) diff --git a/examples/check-expression/check_expression.bal b/examples/check-expression/check_expression.bal index 4dfadf7625..6d68881fa4 100644 --- a/examples/check-expression/check_expression.bal +++ b/examples/check-expression/check_expression.bal @@ -1,15 +1,16 @@ import ballerina/io; -// Same as `intFromBytesWithCheck` but with explicit error handling. function intFromBytes(byte[] bytes) returns int|error { string|error res = string:fromBytes(bytes); - // Handling the error explicitly. + // Explicitly check if the result is an error and + // immediately return if so. if res is error { return res; } return int:fromString(res); } +// Same as `intFromBytes` but with `check` instead of explicitly checking for error and returning. function intFromBytesWithCheck(byte[] bytes) returns int|error { string str = check string:fromBytes(bytes); return int:fromString(str); diff --git a/examples/check-expression/check_expression.md b/examples/check-expression/check_expression.md index 52ae7350c8..df47a34c14 100644 --- a/examples/check-expression/check_expression.md +++ b/examples/check-expression/check_expression.md @@ -1,6 +1,6 @@ # Check expression -If an expression can cause an `error`, you can use the `check` expression to indicate you want to terminate the execution of the current scope with that error as the result. Generally this is done by returning the error value from the current function. +If an expression can evaluate to an error value, you can use the `check` expression to indicate that you want the execution of the current block to terminate with that error as the result. This results in the error value being returned from the current function/worker, unless the `check` expression is used in a failure-handling statement (e.g., statement with on fail, retry statement). ::: code check_expression.bal ::: diff --git a/examples/check-expression/check_expression.out b/examples/check-expression/check_expression.out index ceac7007f2..b763450360 100644 --- a/examples/check-expression/check_expression.out +++ b/examples/check-expression/check_expression.out @@ -1,4 +1,3 @@ $ bal run check_expression.bal error("{ballerina/lang.int}NumberParsingError",message="'string' value 'hello' cannot be converted to 'int'") error("{ballerina/lang.int}NumberParsingError",message="'string' value 'hello' cannot be converted to 'int'") - diff --git a/examples/error-reporting/error_reporting.bal b/examples/error-reporting/error_reporting.bal index 5d9bc49dca..e85a2ff841 100644 --- a/examples/error-reporting/error_reporting.bal +++ b/examples/error-reporting/error_reporting.bal @@ -7,47 +7,51 @@ type Person record { function validatePeople(Person[] people) returns error? { if people.length() == 0 { - // Create error with only a message. - return error("empty people array"); + // Create an error value specifying only the error message. + return error("Expected a non-empty array"); } foreach Person p in people { + io:println("Validating ", p.name); error? err = validatePerson(p); - if err != () { - // Create a new error with previous error as the cause and people in the detail. - return error("failed to validate people", err, people = people); + if err is error { + // Create a new error value with the validation error as the cause + // and the `Person` value for which validation failed in the detail mapping. + return error("Validation failed for a person", err, person = p); } } } function validatePerson(Person person) returns error? { - if person.age < 0 { - // Create a new error we person in the detail to help debugging. - return error("age cannot be negative", person = person); + int age = person.age; + if age < 0 { + // If validation fails for age, create a new error value specifying + // an error message and the age value for which validation failed. + return error("Age cannot be negative", age = age); } } public function main() { Person[] people = [ {name: "Alice", age: 25}, - {name: "Bob", age: -1} + {name: "Bob", age: -1}, + {name: "Charlie", age: 30} ]; error? err = validatePeople(people); - if err != () { + if err is error { printError(err); } } // Helper function to print internals of error value. -function printError(error err, int depth = 0) { - string indent = "".join(...from int _ in 0 ..< depth - select " "); - io:println(indent + "message: ", err.message()); - io:println(indent + "details: ", err.detail()); - io:println(indent + "stack trace: ", err.stackTrace()); +function printError(error err) { + io:println("Message: ", err.message()); + io:println("Detail: ", err.detail()); + io:println("Stack trace: ", err.stackTrace()); + error? cause = err.cause(); - if cause != () { - io:println(indent + "cause: "); - printError(cause, depth + 1); + if cause is error { + io:println("Cause:"); + printError(cause); } } diff --git a/examples/error-reporting/error_reporting.md b/examples/error-reporting/error_reporting.md index 386a6c08e6..8a4faeb247 100644 --- a/examples/error-reporting/error_reporting.md +++ b/examples/error-reporting/error_reporting.md @@ -1,13 +1,14 @@ # Errors -In Ballerina invalid states are represented by `error` values. Each error value has, -1. Message, a human-readable `string` describing the error. -2. Cause, which is an `error` value if this error was caused by another error, otherwise nil. -3. Detail, a mapping value which can be used to provide additional information about the error. + +Ballerina does not have exceptions. Instead functions report invalid states by returning error values. Each error value has, +1. Message, a human-readable `string` value describing the error. +2. Cause, which is an `error` value if this error was caused due to another error, which needs to be propagated, otherwise nil. +3. Detail, a mapping value consisting of additional information about the error. 4. Stack trace, a snapshot of the state of the execution stack when the error value was created. -Error values are immutable. +Error values are immutable. -You can create new error values by calling the error constructor. As the first argument to the error constructor, it expects the `message` string. As the second argument, you can optionally pass in an `error?` value for cause. The remaining named arguments will be used to create the detail record. The stack trace is provided by the runtime. +You can create a new error value using an error constructor. As the first argument to the error constructor, it expects the message string. As the second argument, you can optionally pass in an `error?` value for cause. Subsequent named arguments, if specified, will be used to create the detail mapping. The stack trace is provided by the runtime. ::: code error_reporting.bal ::: diff --git a/examples/error-reporting/error_reporting.out b/examples/error-reporting/error_reporting.out index 5fe7b170a3..609403ab5e 100644 --- a/examples/error-reporting/error_reporting.out +++ b/examples/error-reporting/error_reporting.out @@ -1,8 +1,10 @@ $ bal run error_reporting.bal -message: failed to validate people -details: {"people":[{"name":"Alice","age":25},{"name":"Bob","age":-1}]} -stack trace: [callableName: validatePeople fileName: error_reporting.bal lineNumber: 18,callableName: main fileName: error_reporting.bal lineNumber: 35] -cause: - message: age cannot be negative - details: {"person":{"name":"Bob","age":-1}} - stack trace: [callableName: validatePerson fileName: error_reporting.bal lineNumber: 26,callableName: validatePeople fileName: error_reporting.bal lineNumber: 15,callableName: main fileName: error_reporting.bal lineNumber: 35] +Validating Alice +Validating Bob +Message: Validation failed for a person +Detail: {"person":{"name":"Bob","age":-1}} +Stack trace: [callableName: validatePeople fileName: error_reporting.bal lineNumber: 20,callableName: main fileName: error_reporting.bal lineNumber: 40] +Cause: +Message: Age cannot be negative +Detail: {"age":-1} +Stack trace: [callableName: validatePerson fileName: error_reporting.bal lineNumber: 30,callableName: validatePeople fileName: error_reporting.bal lineNumber: 16,callableName: main fileName: error_reporting.bal lineNumber: 40] diff --git a/examples/error-subtyping/error_subtyping.bal b/examples/error-subtyping/error_subtyping.bal index d1d3a1e89e..5cfb3623b2 100644 --- a/examples/error-subtyping/error_subtyping.bal +++ b/examples/error-subtyping/error_subtyping.bal @@ -9,19 +9,19 @@ type InvalidI32Detail record {| int:Signed32 value; |}; -// `error` with `InvalidIntDetail` as the detail type. +// Error with the `InvalidIntDetail` type as the detail type. type InvalidIntError error; // `error` with `InvalidI32Detail` as the detail type. Thus it is a subtype of `InvalidIntError`. type InvalidI32Error error; -// `error` with `InvalidIntDetail` as the detail type and a unique type ID. -// Therefore this is a proper subtype of `InvalidIntError`, but don't have a subtype relationship +// Distinct error with the `InvalidIntDetail` type as the detail type and a unique type ID. +// Therefore, this is a proper subtype of `InvalidIntError`, but doesn't have a subtype relationship // with `AnotherDistinctIntError` because they have different type IDs. type DistinctIntError distinct error; // Another `error` with `InvalidIntDetail` as the detail type and different type ID to `DistinctIntError` -// This is also a proper subtype of `InvalidIntError`, but don't have a subtype relationship with `DistinctIntError` +// This is also a proper subtype of `InvalidIntError`, but doesn't have a subtype relationship with `DistinctIntError` type AnotherDistinctIntError distinct error; public function main() { @@ -39,7 +39,7 @@ public function main() { io:println(e3 is AnotherDistinctIntError); } -// Helper functions to create errors +// Helper functions to create errors. function createInvalidIntError(int value) returns InvalidIntError { return error("Invalid int", value = value); } @@ -51,4 +51,3 @@ function createDistinctInvalidIntError(int value) returns DistinctIntError { function createInvalidI32Error(int:Signed32 value) returns InvalidI32Error { return error("Invalid i32", value = value); } - diff --git a/examples/error-subtyping/error_subtyping.md b/examples/error-subtyping/error_subtyping.md index 74112f9e95..6cd53ee3a6 100644 --- a/examples/error-subtyping/error_subtyping.md +++ b/examples/error-subtyping/error_subtyping.md @@ -1,8 +1,10 @@ # Error subtyping -If we want to check if a given `error` type (say `e1`) is a supertype of another `error` type (say `e2`), first we need to check if `e1` is a distinct error type. If it's not the case then `e1` is a supertype of `e2` if and only if the detail record type of `e1` is a supertype of the detail record type of `e2`. +If we want to identify if a given `error` type (say `ESub`) is a subtype of another error type (say `ESuper`), first we need to check if `ESuper` is a distinct error type. If it is not then `ESub` is a subtype if and only if the detail type of `ESub` is a subtype of the detail type of `ESuper`. -If more explicit control over error type relations is desired you can use `distinct` error types. Each declaration of a distinct error type has a unique type ID. If the supertype is a `distinct` error type then there is the additional requirement that it must contain all the type IDs of the subtype. Note that you can create subtypes of distinct error types by intersecting them with other error types. +If more explicit control over error type relations is desired you can use `distinct` error types. Each declaration of a distinct error type has a unique type ID. If `ESuper` is a distinct error type there is the additional requirement that type ID of `ESub` must belong to the type ID set of `ESuper` for it to be a subtype. + +Note that you can create subtypes of distinct error types by intersecting them with other error types. ```ballerina diff --git a/examples/error-type-intersection/error_type_intersection.bal b/examples/error-type-intersection/error_type_intersection.bal index 832dc4820c..7049dcc62f 100644 --- a/examples/error-type-intersection/error_type_intersection.bal +++ b/examples/error-type-intersection/error_type_intersection.bal @@ -32,7 +32,7 @@ function createDistinctNumericInputError(int value) returns DistinctNumericInput public function main() { NumericInputError e1 = createNumericInputError(5); - // `e1` belong to `InputError` since it's detail type is a subtype of `InputErrorDetail`. + // `e1` belong to `InputError` since its detail type is a subtype of `InputErrorDetail`. io:println(e1 is InputError); // `e1` doesn't belong to `DistinctInputError` since it doesn't have the type id of `DistinctInputError`. diff --git a/examples/trap-expression/trap_expression.bal b/examples/trap-expression/trap_expression.bal index d9736e0edb..eb0297d7a4 100644 --- a/examples/trap-expression/trap_expression.bal +++ b/examples/trap-expression/trap_expression.bal @@ -5,20 +5,23 @@ function hereBeDragons() returns int { alwaysPanic(); } -// Return type `never` indicate that this function will never return normally. That is it will always panic +// Return type `never` indicate that this function will never return normally. I.e., it will always panic. function alwaysPanic() returns never { panic error("deep down in the code"); } public function main() { - // Division by 0 trigger a panic + // Division by 0 triggers a panic. int|error result = trap 1 / 0; if result is error { io:println("Error: ", result); } - // This will tigger a panic deep within the call stack and we'll trap it here + // Calling the `hereBeDragons` function triggers a panic deep within the call stack, which is trapped here. int|error result2 = trap hereBeDragons(); if result2 is error { io:println("Error: ", result2); } + // This will trigger a panic which is not trapped. Thus it will terminate the program. + int result3 = hereBeDragons(); + io:println("Result: ", result3); } diff --git a/examples/trap-expression/trap_expression.md b/examples/trap-expression/trap_expression.md index 6ec7ca0adf..7699b557b6 100644 --- a/examples/trap-expression/trap_expression.md +++ b/examples/trap-expression/trap_expression.md @@ -1,6 +1,6 @@ # Trap expression -If you have an expression such as a function call that can potentially trigger a `panic` you can use a `trap` expression to prevent further unwinding of the stack. Then if the evaluation of the expression trigger a panic you will get the `error` associated with the panic. Otherwise you will get the result of the expression. +If you have an expression such as a function call that can potentially trigger a panic you can use a `trap` expression to prevent further unwinding of the stack. Then if the evaluation of the expression triggers a panic you will get the `error` value associated with the panic. Otherwise, you will get the result of the expression. ::: code trap_expression.bal ::: diff --git a/examples/trap-expression/trap_expression.out b/examples/trap-expression/trap_expression.out index d8e5f96579..84e1a561ae 100644 --- a/examples/trap-expression/trap_expression.out +++ b/examples/trap-expression/trap_expression.out @@ -1,4 +1,7 @@ $ bal run trap_expression.bal Error: error("{ballerina}DivisionByZero",message=" / by zero") Error: error("deep down in the code") - +error: deep down in the code + at trap_expression:alwaysPanic(trap_expression.bal:10) + trap_expression:hereBeDragons(trap_expression.bal:5) + trap_expression:main(trap_expression.bal:25)