Skip to content

Commit 5f64b96

Browse files
authored
Implement panic expression (#7073)
## Description This PR introduces the `panic` expression to the language, as defined in the [ABI Errors RFC](https://github.com/FuelLabs/sway-rfcs/blob/master/rfcs/0014-abi-errors.md#panic). The `panic` expression can be used without arguments, or accept an argument that implements the `std::marker::Error` trait. The `Error` trait is implemented by the compiler for the unit `()`, string slices, and `#[error_type]` enums. Using the `panic` expression without arguments gives the symetry with the `return` expression and acts in the same way as having unit as an argument. ``` panic; panic (); panic "This is some error."; panic Errors::SomeError(42); ``` Panicking without an argument or with unit as argument is discouraged to use. In the upcoming PR that finalizes the ABI errors feature, we will emit a warning if the `panic` is used without arguments or with unit as argument. `panic` expression is available in all program kinds. In predicates it currently compiles only to revert. Once `__dbg` intrinsic is implemented, we can consider compiling to it in predicates. In the upcoming PR, the `error_codes` entry in the ABI JSON will be available for all program kinds. The dead code analysis for the `panic` expression is implemented in the straightforward way, following the current approach of connecting reverts to the exit node. This will be revisted in a separate PR, together with the open TODOs in the DCA implementation of `Return`. Essentially, we want reverting/panicking to connect to program exit and implicit returns to the exit node. Additionally, the PR: - extends `forc test` CLI attributes with `--revert-codes` that prints revert codes even if tests are successful but revert. - updates outdated "Logs Inside Tests" chapter in the documentation on unit testing. - extends the snapshot testing infrastructure to support `forc test` in snapshot tests. - fixes #7072. Partially addresses #6765. ## Breaking Change `panic` is a new reserved keyword. Once the `error_type` feature becomes integrated, compiling any existing code containing a "panic" as an identifier will result in an "Identifiers cannot be a reserved keyword." error. In this PR, `panic` keyword is hidden behind the `error_type` feature flag. This prevents existing code from breaking, unless opted-in. Note that, although being behind a feature flag, `panic` cannot be used in conditional compilation, means in combination with the `#[cfg(experimental_error_type = true/false)]`. The reason is that `cfg` evaluation happens after parsing, and we can either parse `panic` as a keyword or identifier during the parsing, because we cannot distinguish ambiguous cases like `panic;` or just `panic`. This limitation means, that introducing `panic` to `std` can be done only once the `error_type` feature is fully integrated. In practice, this is not a serious limitation, because introducing `panic` in `std` would anyhow, due to effort and design decisions, happen after the feature is integrated. We will provide a migration for this breaking change that will migrate the existing "panic" identifiers to "r#panic". ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers.
1 parent d258921 commit 5f64b96

File tree

124 files changed

+2325
-428
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+2325
-428
lines changed

docs/book/src/testing/unit-testing.md

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,16 @@ fn test_meaning_of_life() {
7171

7272
Tests with `#[test(should_revert)]` are considered to be passing if they are reverting.
7373

74+
Revert codes are not shown by default in passing tests that have `should_revert`. To see revert codes, use the `--revert-codes` flag, `forc test --revert-codes`:
75+
76+
```console
77+
test test_meaning_of_life ... ok (23.099µs, 0 gas)
78+
Revert code: ffffffffffff0004
79+
```
80+
7481
## Calling Contracts
7582

76-
Unit tests can call contract functions an example for such calls can be seen below.
83+
Unit tests can call contract functions. An example for such calls can be seen below.
7784

7885
```sway
7986
contract;
@@ -96,7 +103,7 @@ To test the `test_function()`, a unit test like the following can be written.
96103
fn test_success() {
97104
let caller = abi(MyContract, CONTRACT_ID);
98105
let result = caller.test_function {}();
99-
assert(result == true)
106+
assert(result == true);
100107
}
101108
```
102109

@@ -107,7 +114,7 @@ It is also possible to test failure with contract calls as well.
107114
fn test_fail() {
108115
let caller = abi(MyContract, CONTRACT_ID);
109116
let result = caller.test_function {}();
110-
assert(result == false)
117+
assert(result == false);
111118
}
112119
```
113120

@@ -154,37 +161,58 @@ fn main() {}
154161
155162
#[test]
156163
fn test_fn() {
157-
let a = 10;
164+
let a = 10;
158165
log(a);
159166
let b = 30;
160167
log(b);
161-
assert_eq(a, 10)
162-
assert_eq(b, 30)
168+
assert_eq(a, 10);
169+
assert_eq(b, 30);
163170
}
164171
```
165172

166-
The example shown above is logging two different variables, `a` and `b` and their values are `10` and `30`, respectively. Without log decoding printed log for this test with `forc test --logs` (`--logs` flag is required to see the logs for this example since the test is passing. Logs are silenced by default in passing tests, and can be enabled using the `--logs` flag.):
173+
The above example shows a passing test that is logging two different variables, `a` and `b`, and their values are `10` and `30`, respectively. Logs are silenced by default in passing tests, and can be enabled using the `--logs` flag, `forc test --logs`:
167174

168175
```console
169-
Finished debug [unoptimized + fuel] target(s) in 5.23s
170-
Bytecode hash: 0x1cb1edc031691c5c08b50fd0f07b02431848ab81b325b72eb3fd233c67d6b548
171-
Running 1 test, filtered 0 tests
172-
test test_fn ... ok (38.875µs, 232 gas)
173-
[{"LogData":{"data":"000000000000000a","digest":"8d85f8467240628a94819b26bee26e3a9b2804334c63482deacec8d64ab4e1e7","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":11032,"ptr":67107840,"ra":0,"rb":0}},{"LogData":{"data":"000000000000001e","digest":"48a97e421546f8d4cae1cf88c51a459a8c10a88442eed63643dd263cef880c1c","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":11516,"ptr":67106816,"ra":0,"rb":1}}]
176+
Running 1 test, filtered 0 tests
177+
test test_fn ... ok (58.842µs, 0 gas)
178+
Decoded log value: 10, log rb: 1515152261580153489
179+
Decoded log value: 30, log rb: 1515152261580153489
174180
```
175181

176-
This is not very easy to understand, it is possible to decode these logs with `--decode` flag, executing `forc test --logs --decode`:
182+
The `--logs` flag prints decoded log values. If you want to see pretty-printed raw log receipts you can use the `--raw-logs --pretty` flags, `forc test --raw-logs --pretty`:
177183

178184
```console
179-
Finished debug [unoptimized + fuel] target(s) in 5.23s
180-
Bytecode hash: 0x1cb1edc031691c5c08b50fd0f07b02431848ab81b325b72eb3fd233c67d6b548
181-
Running 1 test, filtered 0 tests
182-
test test_fn ... ok (38.875µs, 232 gas)
183-
Decoded log value: 10, log rb: 0
184-
Decoded log value: 30, log rb: 1
185+
test test_fn ... ok (54.042µs, 0 gas)
186+
Raw logs:
187+
[
188+
{
189+
"LogData": {
190+
"data": "000000000000000a",
191+
"digest": "8d85f8467240628a94819b26bee26e3a9b2804334c63482deacec8d64ab4e1e7",
192+
"id": "0000000000000000000000000000000000000000000000000000000000000000",
193+
"is": 10368,
194+
"len": 8,
195+
"pc": 11212,
196+
"ptr": 67107840,
197+
"ra": 0,
198+
"rb": 1515152261580153489
199+
}
200+
},
201+
{
202+
"LogData": {
203+
"data": "000000000000001e",
204+
"digest": "48a97e421546f8d4cae1cf88c51a459a8c10a88442eed63643dd263cef880c1c",
205+
"id": "0000000000000000000000000000000000000000000000000000000000000000",
206+
"is": 10368,
207+
"len": 8,
208+
"pc": 11212,
209+
"ptr": 67106816,
210+
"ra": 0,
211+
"rb": 1515152261580153489
212+
}
213+
}
214+
]
185215
```
186216

187-
As it can be seen, the values are human readable and easier to understand which makes debugging much more easier.
188-
189-
**Note**: This is an experimental feature and we are actively working on reporting variable names next to their values.
217+
The `--logs` and `--raw-logs` flags can be combined to print both the decoded and raw logs.
190218
<!-- unit_test_log::example::end -->

forc-plugins/forc-migrate/src/visiting/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,9 @@ impl __ProgramVisitor {
634634
Self::visit_expr(ctx, lexed_returned.__as_ref(), ty_returned, visitor, output)?;
635635
}
636636
}
637+
Expr::Panic { expr_opt: _, .. } => {
638+
// TODO: Implement visiting `panic`.
639+
}
637640
Expr::If(if_expr) => {
638641
Self::visit_if(ctx, if_expr, ty_expr, visitor, output)?;
639642
}

forc/src/cli/commands/test.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ pub struct TestPrintOpts {
7171
/// Print the raw logs for tests.
7272
#[clap(long)]
7373
pub raw_logs: bool,
74+
/// Print the revert codes for tests.
75+
#[clap(long)]
76+
pub revert_codes: bool,
7477
}
7578

7679
pub(crate) fn exec(cmd: Command) -> ForcResult<()> {
@@ -171,11 +174,19 @@ fn print_tested_pkg(pkg: &TestedPackage, test_print_opts: &TestPrintOpts) -> For
171174
}
172175
}
173176

177+
// If raw logs are enabled, print them.
174178
if test_print_opts.raw_logs {
175179
let formatted_logs = format_log_receipts(logs, test_print_opts.pretty_print)?;
176180
info!("Raw logs:\n{}", formatted_logs);
177181
}
178182

183+
// If revert codes are enabled, print them.
184+
if test_print_opts.revert_codes {
185+
if let Some(revert_code) = test.revert_code() {
186+
info!("Revert code: {revert_code:x}");
187+
}
188+
}
189+
179190
// If the test is failing, save the test result for printing the details later on.
180191
if !test_passed {
181192
failed_tests.push(test);

sway-ast/src/expr/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pub enum Expr {
3030
return_token: ReturnToken,
3131
expr_opt: Option<Box<Expr>>,
3232
},
33+
Panic {
34+
panic_token: PanicToken,
35+
expr_opt: Option<Box<Expr>>,
36+
},
3337
If(IfExpr),
3438
Match {
3539
match_token: MatchToken,
@@ -223,6 +227,17 @@ impl Spanned for Expr {
223227
};
224228
Span::join(start, &end)
225229
}
230+
Expr::Panic {
231+
panic_token,
232+
expr_opt,
233+
} => {
234+
let start = panic_token.span();
235+
let end = match expr_opt {
236+
Some(expr) => expr.span(),
237+
None => panic_token.span(),
238+
};
239+
Span::join(start, &end)
240+
}
226241
Expr::If(if_expr) => if_expr.span(),
227242
Expr::Match {
228243
match_token,
@@ -566,6 +581,7 @@ impl Expr {
566581
| Expr::Parens(..)
567582
| Expr::Array(..)
568583
| Expr::Return { .. }
584+
| Expr::Panic { .. }
569585
| Expr::FuncApp { .. }
570586
| Expr::Index { .. }
571587
| Expr::MethodCall { .. }
@@ -613,6 +629,7 @@ impl Expr {
613629
Expr::Array(_) => "array",
614630
Expr::Asm(_) => "assembly block",
615631
Expr::Return { .. } => "return",
632+
Expr::Panic { .. } => "panic",
616633
Expr::If(_) => "if expression",
617634
Expr::Match { .. } => "match expression",
618635
Expr::While { .. } => "while loop",

sway-ast/src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ define_keyword!(ConfigurableToken, "configurable");
8484
define_keyword!(TypeToken, "type");
8585
define_keyword!(PtrToken, "__ptr");
8686
define_keyword!(SliceToken, "__slice");
87+
define_keyword!(PanicToken, "panic");
8788

8889
/// The type is a token.
8990
pub trait Token: Spanned + Sized {

sway-ast/src/pattern.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub enum Pattern {
1515
/// A pattern made of a single ident, which could either be a variable or an enum variant
1616
AmbiguousSingleIdent(Ident),
1717
Var {
18-
reference: Option<RefToken>, // TODO-IG: Implement `ref`, `mut`, and `ref mut` when implementing matching of references.
18+
reference: Option<RefToken>, // TODO: (REFERENCES) Implement `ref`, `mut`, and `ref mut` when implementing matching of references.
1919
mutable: Option<MutToken>,
2020
name: Ident,
2121
},

sway-core/src/abi_generation/abi_str.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ impl TypeInfo {
199199
referenced_type,
200200
} => {
201201
format!(
202-
"__ref {}{}", // TODO-IG: No references in ABIs according to the RFC. Or we want to have them?
202+
"__ref {}{}", // TODO: (REFERENCES) No references in ABIs according to the RFC. Or we want to have them?
203203
if *to_mutable_value { "mut " } else { "" },
204204
referenced_type.abi_str(ctx, engines, false)
205205
)

sway-core/src/abi_generation/evm_abi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ pub fn abi_str(type_info: &TypeInfo, engines: &Engines) -> String {
140140
referenced_type,
141141
} => {
142142
format!(
143-
"__ref {}{}", // TODO-IG: No references in ABIs according to the RFC. Or we want to have them?
143+
"__ref {}{}", // TODO: (REFERENCES) No references in ABIs according to the RFC. Or we want to have them?
144144
if *to_mutable_value { "mut " } else { "" },
145145
abi_str_type_arg(referenced_type, engines)
146146
)

sway-core/src/asm_generation/fuel/fuel_asm_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2094,7 +2094,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> {
20942094

20952095
// ---------------------------------------------------------------------------------------------
20962096

2097-
// TODO-IG: Reassess all the places we use `is_copy_type`.
2097+
// TODO: (REFERENCES) Reassess all the places where we use `is_copy_type`.
20982098
pub(crate) fn is_copy_type(&self, ty: &Type) -> bool {
20992099
ty.is_unit(self.context)
21002100
|| ty.is_never(self.context)

sway-core/src/control_flow_analysis/analyze_return_paths.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ impl<'cfg> ControlFlowGraph<'cfg> {
136136
enum NodeConnection {
137137
/// This represents a node that steps on to the next node.
138138
NextStep(Option<NodeIndex>),
139-
/// This represents a return or implicit return node, which aborts the stepwise flow.
139+
/// This represents a node which aborts the stepwise flow.
140+
/// Such nodes are:
141+
/// - return expressions,
142+
/// - implicit returns,
143+
/// - panic expressions.
140144
Return(NodeIndex),
141145
}
142146

@@ -154,6 +158,10 @@ fn connect_node<'eng: 'cfg, 'cfg>(
154158
| ty::TyAstNodeContent::Expression(ty::TyExpression {
155159
expression: ty::TyExpressionVariant::ImplicitReturn(..),
156160
..
161+
})
162+
| ty::TyAstNodeContent::Expression(ty::TyExpression {
163+
expression: ty::TyExpressionVariant::Panic(..),
164+
..
157165
}) => {
158166
let this_index = graph.add_node(ControlFlowGraphNode::from_node(node));
159167
if let Some(leaf_ix) = leaf_opt {

0 commit comments

Comments
 (0)