Debug trait and its auto implementation (#7015)

## Description

This PR implements the `__dbg(...)` intrinsic, which is very similar to
Rust `dbg!(...)` macro.

Up until now, it has being the norm to use `__log` to debug values.
Given that this is NOT the first use case for log, we have always found
some issues with it: log does not work on predicates, log does not show
"private" fields like `Vec::capacity` and others.

To solve these problems `__dbg` is being introduced:
1 - it will work on all program types, including predicates;
2 - it also prints the file name, line and column;
3 - All types will have an automatic implementation of Debug if
possible, which can still be customized.
4 - Even `raw_ptr` and other non "loggable" types, have `Debug` impls.
5 - `__dbg` will be completely stripped in the release build by default.
It can be turned on again if needed.

So this:

```
// Aggregates
let _ = __dbg((1u64, 2u64));
let _ = __dbg([1u64, 2u64]);

// Structs and Enums
let _ = __dbg(S { });
let _ = __dbg(E::None);
let _ = __dbg(E::Some(S { }));
```

will generate this:

```
[src/main.sw:19:13] = (1, 2)
[src/main.sw:20:13] = [1, 2]
[src/main.sw:23:13] = S { }
[src/main.sw:24:13] = None
[src/main.sw:25:13] = E(S { })
```

How does this work?

`__dbg(value)` intrinsic is desugared into `{ let f = Formatter{};
f.print_str(...); let value = value; value.fmt(f); value }`.

`Formatter` is similar to Rust's one. The difference is that we still do
not support string formatting, so the `Formatter` has a lot of `print_*`
functions.

And each `print` function calls a "syscall". This `syscall` uses `ecal`
under the hood and it follows unix write syscall schema.

```sway
// ssize_t write(int fd, const void buf[.count], size_t count);
fn syscall_write(fd: u64, buf: raw_ptr, count: u64) {
    asm(id: 1000, fd: fd, buf: buf, count: count) {
        ecal id fd buf count;
    }
}
```

For that to work, the VM interpreter must have its `EcalState` setup and
interpret syscall number 1000 as `write`. This PR does this for `forc
test` and our `e2e test suite`.

Each test in `forc test` will capture these calls and only print to the
terminal when requested with the `--log` flag.

## Garbage Collector and auto generated

Before, we were associating all auto-generated code with a pseudo file
called "<autogenerated>.sw" that was never garbage collected.

This generated a problem inside the LSP when the `auto_impl.rs` ran a
second time because of a collision in the "shareable type" map. When we
try to solve this collision, choosing to keep the old value or to insert
the new, the type inside the map points to already collected types and
the compiler panics. This is a known problem.

The workaround for this is to break the auto-generated code into
multiple files. Now they are named "main.autogenerated.sw", for example.
We create one pseudo-file for each real file that needs one.

When we garbage collect one file, `main.sw`, for example, we also
collect its associated auto-generated file.

## Checklist

- [ ] 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.
- [ ] 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.
This commit is contained in:
Daniel Frederico Lins Leite 2025-04-22 07:49:31 -03:00 committed by GitHub
parent f607a674e3
commit f736fce7ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
78 changed files with 4132 additions and 161 deletions

View file

@ -244,4 +244,6 @@ StringArray
StringSlice
calldata
Cfg
evm
evm
AbiEncode
AbiDecode

View file

@ -11,3 +11,48 @@ node to exercise your Sway code. Instruction-by-instruction debugging is availab
- [Debugging with CLI](./debugging_with_cli.md)
- [Debugging with IDE](./debugging_with_ide.md)
## `__dbg` intrinsic function
Sway also offers the `__dbg` intrinsic function to help debug all applications types: scripts, contracts and predicates.
When called, this intrinsic function will print the current file, line and column, together with a customizable print of the specified value.
```sway
script;
fn main() -> u64 {
__dbg(1u64)
}
```
The application above will print:
```terminal
[src/main.sw:3:5] = 1
```
Structs can be customized by implementing the `Debug` trait.
```sway
script;
struct S { }
impl Debug for S {
fn fmt(self, ref mut f: Formatter) {
f.debug_struct("S2")
.field("field1", 1)
.field("field2", "Hello")
.finish();
}
}
fn main() -> u64 {
let _ = __dbg(S {});
__dbg(1u64)
}
```
This code is very similar to what the Sway compiler generates by default for all declared types.
And this is what is printed:
```terminal
[src/main.sw:12:13] = S2 { field1: 1, field2: "Hello" }
[src/main.sw:13:5] = 1
```

View file

@ -165,12 +165,14 @@ __state_store_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool
---
```sway
__log<T>(val: T)
__log<T>(val: T) where T: AbiEncode
```
**Description:** Logs value `val`.
**Constraints:** None.
**Constraints:**
- `T` must implement AbiEncode
---
@ -335,10 +337,10 @@ __jmp_mem()
---
```sway
__slice(item: &[T; N], start: u64, end: u64) -> &[T]
__slice(item: &[T], start: u64, end: u64) -> &[T]
__slice(item: &mut [T; N], start: u64, end: u64) -> &mut [T]
__slice(item: &mut [T], start: u64, end: u64) -> &mut [T]
__slice<T>(item: &[T; N], start: u64, end: u64) -> &[T]
__slice<T>(item: &[T], start: u64, end: u64) -> &[T]
__slice<T>(item: &mut [T; N], start: u64, end: u64) -> &mut [T]
__slice<T>(item: &mut [T], start: u64, end: u64) -> &mut [T]
```
**Description:** Slices an array or another slice.
@ -358,10 +360,10 @@ Runtime bound checks are not generated, and must be done manually when and where
---
```sway
__elem_at(item: &[T; N], index: u64) -> &T
__elem_at(item: &[T], index: u64) -> &T
__elem_at(item: &mut [T; N], index: u64) -> &mut T
__elem_at(item: &mut [T], index: u64) -> &mut T
__elem_at<T>(item: &[T; N], index: u64) -> &T
__elem_at<T>(item: &[T], index: u64) -> &T
__elem_at<T>(item: &mut [T; N], index: u64) -> &mut T
__elem_at<T>(item: &mut [T], index: u64) -> &mut T
```
**Description:** Returns a reference to the indexed element. The mutability of reference is defined by the first parameter mutability.
@ -372,3 +374,37 @@ Runtime bound checks are not generated, and must be done manually when and where
- `item` is a reference to an array or a reference to a slice;
- when `index` is a literal, it must be smaller than `item` length;
---
```sway
__dbg<T>(value: T) -> T where T: Debug
```
**Description:** Automatically calls the `Debug` trait on the passed `value`, with file, line and column information. The passed value is returned without any modification, allowing `__dbg(...)` to be used inside of any expression.
The code generated by this intrinsic function varies with the compilation mode. For example:
```terminal
forc build <- will print everything as expected
forc build --release <- nothing will be printed
```
To enable code generation even on `Release` builds, the flag `force-dbg-in-release` needs to be enabled inside `forc.toml`.
Example:
```toml
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
entry = "main.sw"
name = "some-project"
force-dbg-in-release = true
```
It is strongly suggested to always remove this flag before publishing binaries as it will not have any effect when running
on real nodes and it only increases gas usage.
**Constraints:**
- `T` must implement Debug

View file

@ -32,9 +32,3 @@ An analogy for predicates is rather than a traditional 12 or 24 word seed phrase
Predicates may introspect the transaction spending their coins (inputs, outputs, script bytecode, etc.) and may take runtime arguments, either or both of which may affect the evaluation of the predicate.
It is important to note that predicates cannot read or write memory. They may however check the inputs and outputs of a transaction. For example in the [OTC Predicate Swap Example](https://github.com/FuelLabs/sway-applications/tree/master/OTC-swap-predicate), a user may specify they would like to swap `asset1` for `asset2` and with amount of `5`. The user would then send `asset1` to the predicate. Only when the predicate can verify that the outputs include `5` coins of `asset2` being sent to the original user, may `asset1` be transferred out of the predicate.
## Debugging Predicates
Because they don't have any side effects (they are _pure_), predicates cannot create receipts. Therefore, they cannot have logging or create a stack backtrace. This means that there is no native way to debug them aside from using a single-stepping debugger.
As a workaround, the predicate can be written, tested, and debugged first as a `script`, and then changed back into a `predicate`.

View file

@ -83,6 +83,10 @@ impl BuildProfile {
optimization_level: OptLevel::Opt1,
}
}
pub fn is_release(&self) -> bool {
self.name == Self::RELEASE
}
}
impl Default for BuildProfile {

View file

@ -213,6 +213,7 @@ pub struct Project {
#[serde(default)]
pub experimental: HashMap<String, bool>,
pub metadata: Option<toml::Value>,
pub force_dbg_in_release: Option<bool>,
}
// Validation function for the `name` field
@ -1449,6 +1450,7 @@ mod tests {
forc_version: None,
experimental: HashMap::new(),
metadata: Some(toml::Value::from(toml::value::Table::new())),
force_dbg_in_release: None,
};
let serialized = toml::to_string(&project).unwrap();
@ -1477,6 +1479,7 @@ mod tests {
forc_version: None,
experimental: HashMap::new(),
metadata: None,
force_dbg_in_release: None,
};
let serialized = toml::to_string(&project).unwrap();
@ -1529,7 +1532,7 @@ mod tests {
name = "test-project"
license = "Apache-2.0"
entry = "main.sw"
[metadata
description = "Invalid TOML"
"#;
@ -1542,7 +1545,7 @@ mod tests {
name = "test-project"
license = "Apache-2.0"
entry = "main.sw"
[metadata]
] = "Invalid key"
"#;
@ -1555,7 +1558,7 @@ mod tests {
name = "test-project"
license = "Apache-2.0"
entry = "main.sw"
[metadata]
nested = { key = "value1" }
@ -1578,7 +1581,7 @@ mod tests {
name = "test-project"
license = "Apache-2.0"
entry = "main.sw"
[metadata]
boolean = true
integer = 42
@ -1614,13 +1617,13 @@ mod tests {
let toml_str = r#"
[workspace]
members = ["package1", "package2"]
[workspace.metadata]
description = "A test workspace"
version = "1.0.0"
authors = ["Test Author"]
homepage = "https://example.com"
[workspace.metadata.ci]
workflow = "main"
timeout = 3600
@ -1659,7 +1662,7 @@ mod tests {
let toml_str = r#"
[workspace]
members = ["package1", "package2"]
[workspace.metadata]
"#;
@ -1674,15 +1677,15 @@ mod tests {
let toml_str = r#"
[workspace]
members = ["package1", "package2"]
[workspace.metadata]
numbers = [1, 2, 3]
strings = ["a", "b", "c"]
mixed = [1, "two", true]
[workspace.metadata.nested]
key = "value"
[workspace.metadata.nested.deep]
another = "value"
"#;

View file

@ -47,7 +47,7 @@ use sway_core::{
source_map::SourceMap,
write_dwarf, BuildTarget, Engines, FinalizedEntry, LspConfig,
};
use sway_core::{set_bytecode_configurables_offset, PrintAsm, PrintIr};
use sway_core::{set_bytecode_configurables_offset, DbgGeneration, PrintAsm, PrintIr};
use sway_error::{error::CompileError, handler::Handler, warning::CompileWarning};
use sway_features::ExperimentalFeatures;
use sway_types::{Ident, ProgramId, Span, Spanned};
@ -1557,6 +1557,7 @@ pub fn sway_build_config(
entry_path: &Path,
build_target: BuildTarget,
build_profile: &BuildProfile,
dbg_generation: sway_core::DbgGeneration,
) -> Result<sway_core::BuildConfig> {
// Prepare the build config to pass through to the compiler.
let file_name = find_file_name(manifest_dir, entry_path)?;
@ -1564,6 +1565,7 @@ pub fn sway_build_config(
file_name.to_path_buf(),
manifest_dir.to_path_buf(),
build_target,
dbg_generation,
)
.with_print_dca_graph(build_profile.print_dca_graph.clone())
.with_print_dca_graph_url_format(build_profile.print_dca_graph_url_format.clone())
@ -1603,6 +1605,7 @@ pub fn dependency_namespace(
contract_id_value: Option<ContractIdConst>,
program_id: ProgramId,
experimental: ExperimentalFeatures,
dbg_generation: sway_core::DbgGeneration,
) -> Result<namespace::Package, vec1::Vec1<CompileError>> {
// TODO: Clean this up when config-time constants v1 are removed.
let node_idx = &graph[node];
@ -1614,6 +1617,7 @@ pub fn dependency_namespace(
program_id,
contract_id_value,
experimental,
dbg_generation,
)?
} else {
Package::new(name.clone(), None, program_id, false)
@ -1646,6 +1650,7 @@ pub fn dependency_namespace(
program_id,
contract_id_value,
experimental,
dbg_generation,
)?
}
};
@ -1680,12 +1685,18 @@ pub fn compile(
namespace: namespace::Package,
source_map: &mut SourceMap,
experimental: ExperimentalFeatures,
dbg_generation: DbgGeneration,
) -> Result<CompiledPackage> {
let mut metrics = PerformanceData::default();
let entry_path = pkg.manifest_file.entry_path();
let sway_build_config =
sway_build_config(pkg.manifest_file.dir(), &entry_path, pkg.target, profile)?;
let sway_build_config = sway_build_config(
pkg.manifest_file.dir(),
&entry_path,
pkg.target,
profile,
dbg_generation,
)?;
let terse_mode = profile.terse;
let reverse_results = profile.reverse_results;
let fail = |handler: Handler| {
@ -2374,6 +2385,10 @@ pub fn build(
let pkg = &plan.graph()[node];
let manifest = &plan.manifest_map()[&pkg.id()];
let program_ty = manifest.program_type().ok();
let dbg_generation = match (profile.is_release(), manifest.project.force_dbg_in_release) {
(true, Some(true)) | (false, _) => DbgGeneration::Full,
(true, _) => DbgGeneration::None,
};
print_compiling(
program_ty.as_ref(),
@ -2439,6 +2454,7 @@ pub fn build(
None,
program_id,
experimental,
dbg_generation,
) {
Ok(o) => o,
Err(errs) => return fail(&[], &errs),
@ -2451,6 +2467,7 @@ pub fn build(
dep_namespace,
&mut source_map,
experimental,
dbg_generation,
)?;
if let Some(outfile) = profile.metrics_outfile {
@ -2509,6 +2526,7 @@ pub fn build(
contract_id_value.clone(),
program_id,
experimental,
dbg_generation,
) {
Ok(o) => o,
Err(errs) => {
@ -2530,6 +2548,7 @@ pub fn build(
dep_namespace,
&mut source_map,
experimental,
dbg_generation,
)?;
if let Some(outfile) = profile.metrics_outfile {
@ -2577,6 +2596,7 @@ pub fn check(
retrigger_compilation: Option<Arc<AtomicBool>>,
experimental: &[sway_features::Feature],
no_experimental: &[sway_features::Feature],
dbg_generation: sway_core::DbgGeneration,
) -> anyhow::Result<Vec<(Option<Programs>, Handler)>> {
let mut lib_namespace_map = HashMap::default();
let mut source_map = SourceMap::new();
@ -2625,6 +2645,7 @@ pub fn check(
contract_id_value,
program_id,
experimental,
dbg_generation,
)
.expect("failed to create dependency namespace");
@ -2638,6 +2659,7 @@ pub fn check(
&manifest.entry_path(),
build_target,
&profile,
dbg_generation,
)?
.with_include_tests(include_tests)
.with_lsp_mode(lsp_mode.clone());

View file

@ -110,6 +110,7 @@ pub fn compile_html(
None,
&build_instructions.experimental.experimental,
&build_instructions.experimental.no_experimental,
sway_core::DbgGeneration::Full,
)?;
let raw_docs = if build_instructions.no_deps {

View file

@ -108,6 +108,7 @@ pub(crate) fn compile_package<'a>(
None,
&build_instructions.experimental.experimental,
&build_instructions.experimental.no_experimental,
sway_core::DbgGeneration::Full,
)?;
let Some(programs) =

161
forc-test/src/ecal.rs Normal file
View file

@ -0,0 +1,161 @@
use fuel_vm::{
interpreter::EcalHandler,
prelude::{Interpreter, RegId},
};
// ssize_t write(int fd, const void buf[.count], size_t count);
pub const WRITE_SYSCALL: u64 = 1000;
#[derive(Debug, Clone)]
pub enum Syscall {
Write { fd: u64, bytes: Vec<u8> },
Unknown { ra: u64, rb: u64, rc: u64, rd: u64 },
}
impl Syscall {
pub fn apply(&self) {
match self {
Syscall::Write { fd, bytes } => {
let s = std::str::from_utf8(bytes.as_slice()).unwrap();
use std::io::Write;
use std::os::fd::FromRawFd;
let mut f = unsafe { std::fs::File::from_raw_fd(*fd as i32) };
write!(&mut f, "{}", s).unwrap();
// Dont close the fd
std::mem::forget(f);
}
Syscall::Unknown { ra, rb, rc, rd } => {
println!("Unknown ecal: {} {} {} {}", ra, rb, rc, rd);
}
}
}
}
/// Handle VM `ecal` as syscalls.
///
/// The application of the syscalls can be turned off,
/// guaranteeing total isolation from the outside world.
///
/// Capture of the syscalls can be turned on, allowing
/// its application even after the VM is not running anymore.
///
/// Supported syscalls:
/// 1000 - write(fd: u64, buf: raw_ptr, count: u64) -> u64
#[derive(Debug, Clone)]
pub struct EcalSyscallHandler {
pub apply: bool,
pub capture: bool,
pub captured: Vec<Syscall>,
}
impl Default for EcalSyscallHandler {
fn default() -> Self {
Self::only_capturing()
}
}
impl EcalSyscallHandler {
pub fn only_capturing() -> Self {
Self {
apply: false,
capture: true,
captured: vec![],
}
}
pub fn only_applying() -> Self {
Self {
apply: true,
capture: false,
captured: vec![],
}
}
pub fn clear(&mut self) {
self.captured.clear();
}
}
impl EcalHandler for EcalSyscallHandler {
fn ecal<M, S, Tx>(
vm: &mut Interpreter<M, S, Tx, Self>,
a: RegId,
b: RegId,
c: RegId,
d: RegId,
) -> fuel_vm::error::SimpleResult<()>
where
M: fuel_vm::prelude::Memory,
{
let regs = vm.registers();
let syscall = match regs[a.to_u8() as usize] {
WRITE_SYSCALL => {
let fd = regs[b.to_u8() as usize];
let addr = regs[c.to_u8() as usize];
let count = regs[d.to_u8() as usize];
let bytes = vm.memory().read(addr, count).unwrap().to_vec();
Syscall::Write { fd, bytes }
}
_ => {
let ra = regs[a.to_u8() as usize];
let rb = regs[b.to_u8() as usize];
let rc = regs[c.to_u8() as usize];
let rd = regs[d.to_u8() as usize];
Syscall::Unknown { ra, rb, rc, rd }
}
};
let s = vm.ecal_state_mut();
if s.apply {
syscall.apply();
}
if s.capture {
s.captured.push(syscall);
}
Ok(())
}
}
#[test]
fn ok_capture_ecals() {
use fuel_vm::fuel_asm::op::*;
use fuel_vm::prelude::*;
let vm: Interpreter<MemoryInstance, MemoryStorage, Script, EcalSyscallHandler> = <_>::default();
let test_input = "Hello, WriteSyscall!";
let script_data: Vec<u8> = test_input.bytes().collect();
let script = vec![
movi(0x20, WRITE_SYSCALL as u32),
gtf_args(0x10, 0x00, GTFArgs::ScriptData),
movi(0x21, script_data.len().try_into().unwrap()),
ecal(0x20, 0x1, 0x10, 0x21),
ret(RegId::ONE),
]
.into_iter()
.collect();
// Execute transaction
let mut client = MemoryClient::from_txtor(vm.into());
let tx = TransactionBuilder::script(script, script_data)
.script_gas_limit(1_000_000)
.add_fee_input()
.finalize()
.into_checked(Default::default(), &ConsensusParameters::standard())
.expect("failed to generate a checked tx");
let _ = client.transact(tx);
// Verify
let t: Transactor<MemoryInstance, MemoryStorage, Script, EcalSyscallHandler> = client.into();
let syscalls = t.interpreter().ecal_state().captured.clone();
assert_eq!(syscalls.len(), 1);
assert!(
matches!(&syscalls[0], Syscall::Write { fd: 1, bytes } if std::str::from_utf8(bytes).unwrap() == test_input)
);
}

View file

@ -1,3 +1,4 @@
use crate::ecal::EcalSyscallHandler;
use crate::maxed_consensus_params;
use crate::setup::TestSetup;
use crate::TestResult;
@ -9,11 +10,8 @@ use fuel_vm::fuel_asm;
use fuel_vm::prelude::Instruction;
use fuel_vm::prelude::RegId;
use fuel_vm::{
self as vm,
checked_transaction::builder::TransactionBuilderExt,
interpreter::{Interpreter, NotSupportedEcal},
prelude::SecretKey,
storage::MemoryStorage,
self as vm, checked_transaction::builder::TransactionBuilderExt, interpreter::Interpreter,
prelude::SecretKey, storage::MemoryStorage,
};
use rand::{Rng, SeedableRng};
@ -26,7 +24,7 @@ use vm::state::ProgramState;
/// An interface for executing a test within a VM [Interpreter] instance.
#[derive(Debug, Clone)]
pub struct TestExecutor {
pub interpreter: Interpreter<MemoryInstance, MemoryStorage, tx::Script, NotSupportedEcal>,
pub interpreter: Interpreter<MemoryInstance, MemoryStorage, tx::Script, EcalSyscallHandler>,
pub tx: vm::checked_transaction::Ready<tx::Script>,
pub test_entry: PkgTestEntry,
pub name: String,
@ -212,6 +210,7 @@ impl TestExecutor {
condition,
logs,
gas_used,
ecal: Box::new(self.interpreter.ecal_state().clone()),
}))
}
@ -243,10 +242,13 @@ impl TestExecutor {
condition,
logs,
gas_used,
ecal: Box::new(self.interpreter.ecal_state().clone()),
}))
}
pub fn execute(&mut self) -> anyhow::Result<TestResult> {
self.interpreter.ecal_state_mut().clear();
let start = std::time::Instant::now();
let mut state = Ok(self.single_step_until_test());
@ -282,6 +284,7 @@ impl TestExecutor {
condition,
logs,
gas_used,
ecal: Box::new(self.interpreter.ecal_state().clone()),
})
}

View file

@ -1,3 +1,4 @@
pub mod ecal;
pub mod execute;
pub mod setup;
@ -5,6 +6,7 @@ use crate::execute::TestExecutor;
use crate::setup::{
ContractDeploymentSetup, ContractTestSetup, DeploymentSetup, ScriptTestSetup, TestSetup,
};
use ecal::EcalSyscallHandler;
use forc_pkg::{self as pkg, BuildOpts};
use fuel_abi_types::error_codes::ErrorSignal;
use fuel_tx as tx;
@ -74,6 +76,8 @@ pub struct TestResult {
pub logs: Vec<fuel_tx::Receipt>,
/// Gas used while executing this test.
pub gas_used: u64,
/// EcalState of the execution
pub ecal: Box<EcalSyscallHandler>,
}
const TEST_METADATA_SEED: u64 = 0x7E57u64;

View file

@ -165,6 +165,10 @@ fn print_tested_pkg(pkg: &TestedPackage, test_print_opts: &TestPrintOpts) -> For
info!("Decoded log value: {}, log rb: {}", var_value, rb);
}
}
for captured in test.ecal.captured.iter() {
captured.apply();
}
}
if test_print_opts.raw_logs {

View file

@ -50,6 +50,7 @@ pub fn check(
None,
&experimental.experimental,
&experimental.no_experimental,
sway_core::DbgGeneration::None,
)?;
let (res, handler) = v
.pop()

View file

@ -45,6 +45,7 @@ pub enum Intrinsic {
Slice, // let ref_to_slice = __slice::<T: array or ref_to_slice>(item: T, inclusive_start_index, exclusive_end_index)
ElemAt, // let elem: &T = __elem_at::<T: array or ref_to_slice>(item: T, index)
Transmute, // let dst: B = __transmute::<A, B>(src)
Dbg, // __dbg(value)
}
impl fmt::Display for Intrinsic {
@ -92,6 +93,7 @@ impl fmt::Display for Intrinsic {
Intrinsic::Slice => "slice",
Intrinsic::ElemAt => "elem_at",
Intrinsic::Transmute => "transmute",
Intrinsic::Dbg => "dbg",
};
write!(f, "{s}")
}
@ -143,6 +145,7 @@ impl Intrinsic {
"__slice" => Slice,
"__elem_at" => ElemAt,
"__transmute" => Transmute,
"__dbg" => Dbg,
_ => return None,
})
}

View file

@ -38,6 +38,13 @@ impl BuildTarget {
pub const CFG: &'static [&'static str] = &["evm", "fuel"];
}
#[derive(Default, Clone, Copy)]
pub enum DbgGeneration {
Full,
#[default]
None,
}
#[derive(Serialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum OptLevel {
#[default]
@ -180,6 +187,7 @@ impl From<&PrintIr> for PrintPassesOpts {
pub struct BuildConfig {
// Build target for code generation.
pub(crate) build_target: BuildTarget,
pub(crate) dbg_generation: DbgGeneration,
// The canonical file path to the root module.
// E.g. `/home/user/project/src/main.sw`.
pub(crate) canonical_root_module: Arc<PathBuf>,
@ -210,6 +218,7 @@ impl BuildConfig {
root_module: PathBuf,
canonical_manifest_dir: PathBuf,
build_target: BuildTarget,
dbg_generation: DbgGeneration,
) -> Self {
assert!(
canonical_manifest_dir.has_root(),
@ -230,6 +239,7 @@ impl BuildConfig {
};
Self {
build_target,
dbg_generation,
canonical_root_module: Arc::new(canonical_root_module),
print_dca_graph: None,
print_dca_graph_url_format: None,
@ -348,6 +358,7 @@ mod test {
root_module,
canonical_manifest_dir,
BuildTarget::default(),
DbgGeneration::Full,
);
}
@ -359,6 +370,7 @@ mod test {
root_module,
canonical_manifest_dir,
BuildTarget::default(),
DbgGeneration::Full,
);
}
}

View file

@ -52,11 +52,29 @@ impl Engines {
/// Removes all data associated with `source_id` from the engines.
/// It is intended to be used during garbage collection to remove any data that is no longer needed.
///
/// It will also clear the associated autogenerated file for the `source_id` parameter.
pub fn clear_module(&mut self, source_id: &sway_types::SourceId) {
self.type_engine.clear_module(source_id);
self.decl_engine.clear_module(source_id);
self.parsed_decl_engine.clear_module(source_id);
self.query_engine.clear_module(source_id);
// Check if `source_id` has an associated autogenerated file
// and clear it
if let Some(autogenerated_source_id) =
self.se().get_associated_autogenerated_source_id(source_id)
{
if autogenerated_source_id == *source_id {
return;
}
self.type_engine.clear_module(&autogenerated_source_id);
self.decl_engine.clear_module(&autogenerated_source_id);
self.parsed_decl_engine
.clear_module(&autogenerated_source_id);
self.query_engine.clear_module(&autogenerated_source_id);
}
}
/// Helps out some `thing: T` by adding `self` as context.

View file

@ -1677,6 +1677,9 @@ fn const_eval_intrinsic(
let c = transmute_bytes(lookup.context, &mut cursor, &dst_ir_type)?;
Ok(Some(Constant::unique(lookup.context, c)))
}
Intrinsic::Dbg => {
unreachable!("__dbg should not exist in the typed tree")
}
}
}

View file

@ -2210,6 +2210,9 @@ impl<'eng> FnCompiler<'eng> {
Intrinsic::Transmute => {
self.compile_intrinsic_transmute(arguments, return_type, context, md_mgr, &span)
}
Intrinsic::Dbg => {
unreachable!("__dbg should not exist in the typed tree")
}
}
}

View file

@ -31,6 +31,7 @@ use crate::source_map::SourceMap;
pub use asm_generation::from_ir::compile_ir_context_to_finalized_asm;
use asm_generation::FinalizedAsm;
pub use asm_generation::{CompiledBytecode, FinalizedEntry};
pub use build_config::DbgGeneration;
pub use build_config::{BuildConfig, BuildTarget, LspConfig, OptLevel, PrintAsm, PrintIr};
use control_flow_analysis::ControlFlowGraph;
pub use debug_generation::write_dwarf;
@ -98,9 +99,9 @@ pub fn parse(
experimental: ExperimentalFeatures,
) -> Result<(lexed::LexedProgram, parsed::ParseProgram), ErrorEmitted> {
match config {
None => parse_in_memory(handler, engines, src, experimental),
None => parse_in_memory(handler, engines, src, experimental, DbgGeneration::None),
// When a `BuildConfig` is given,
// the module source may declare `dep`s that must be parsed from other files.
// the module source may declare `mod`s that must be parsed from other files.
Some(config) => parse_module_tree(
handler,
engines,
@ -108,6 +109,7 @@ pub fn parse(
config.canonical_root_module(),
None,
config.build_target,
config.dbg_generation,
config.include_tests,
experimental,
config.lsp_mode.as_ref(),
@ -362,6 +364,7 @@ fn parse_in_memory(
engines: &Engines,
src: Source,
experimental: ExperimentalFeatures,
dbg_generation: DbgGeneration,
) -> Result<(lexed::LexedProgram, parsed::ParseProgram), ErrorEmitted> {
let mut hasher = DefaultHasher::new();
src.text.hash(&mut hasher);
@ -376,7 +379,7 @@ fn parse_in_memory(
let attributes_error_emitted = handler.append(attributes_handler);
let (kind, tree) = to_parsed_lang::convert_parse_tree(
&mut to_parsed_lang::Context::new(BuildTarget::EVM, experimental),
&mut to_parsed_lang::Context::new(BuildTarget::EVM, dbg_generation, experimental),
handler,
engines,
module.value.clone(),
@ -425,6 +428,7 @@ fn parse_submodules(
module: &sway_ast::Module,
module_dir: &Path,
build_target: BuildTarget,
dbg_generation: DbgGeneration,
include_tests: bool,
experimental: ExperimentalFeatures,
lsp_mode: Option<&LspConfig>,
@ -457,6 +461,7 @@ fn parse_submodules(
submod_path.clone(),
Some(submod.name.as_str()),
build_target,
dbg_generation,
include_tests,
experimental,
lsp_mode,
@ -510,6 +515,7 @@ fn parse_module_tree(
path: Arc<PathBuf>,
module_name: Option<&str>,
build_target: BuildTarget,
dbg_generation: DbgGeneration,
include_tests: bool,
experimental: ExperimentalFeatures,
lsp_mode: Option<&LspConfig>,
@ -530,6 +536,7 @@ fn parse_module_tree(
&module.value,
module_dir,
build_target,
dbg_generation,
include_tests,
experimental,
lsp_mode,
@ -544,7 +551,7 @@ fn parse_module_tree(
// Convert from the raw parsed module to the `ParseTree` ready for type-check.
let (kind, tree) = to_parsed_lang::convert_parse_tree(
&mut to_parsed_lang::Context::new(build_target, experimental),
&mut to_parsed_lang::Context::new(build_target, dbg_generation, experimental),
handler,
engines,
module.value.clone(),

View file

@ -13,7 +13,7 @@ use sway_error::{
error::CompileError,
handler::{ErrorEmitted, Handler},
};
use sway_types::{BaseIdent, Named, ProgramId, Span, Spanned};
use sway_types::{BaseIdent, Named, SourceId, Span, Spanned};
#[derive(Default)]
pub struct AbiEncodingAutoImplInfo {}
@ -190,7 +190,7 @@ where
}
// Auto implements AbiEncode and AbiDecode for structs and returns their `AstNode`s.
fn auto_impl_struct(
fn auto_impl_abi_encode_and_decode_for_struct(
&mut self,
engines: &Engines,
decl: &TyDecl,
@ -208,16 +208,18 @@ where
let implementing_for_decl_id = decl.to_struct_decl(&Handler::default(), engines).unwrap();
let struct_decl = self.ctx.engines().de().get(&implementing_for_decl_id);
let program_id = struct_decl.span().source_id().map(|sid| sid.program_id());
let abi_encode_body = self.generate_abi_encode_struct_body(engines, &struct_decl);
let abi_encode_code = self.generate_abi_encode_code(
struct_decl.name(),
&struct_decl.type_parameters,
abi_encode_body,
);
let abi_encode_node =
self.parse_impl_trait_to_ty_ast_node(engines, program_id, &abi_encode_code);
let abi_encode_node = self.parse_impl_trait_to_ty_ast_node(
engines,
struct_decl.span().source_id(),
&abi_encode_code,
crate::build_config::DbgGeneration::None,
);
let abi_decode_body = self.generate_abi_decode_struct_body(engines, &struct_decl);
let abi_decode_code = self.generate_abi_decode_code(
@ -225,13 +227,17 @@ where
&struct_decl.type_parameters,
abi_decode_body?,
);
let abi_decode_node =
self.parse_impl_trait_to_ty_ast_node(engines, program_id, &abi_decode_code);
let abi_decode_node = self.parse_impl_trait_to_ty_ast_node(
engines,
struct_decl.span().source_id(),
&abi_decode_code,
crate::build_config::DbgGeneration::None,
);
Some((abi_encode_node.ok(), abi_decode_node.ok()))
}
fn auto_impl_enum(
fn auto_impl_abi_encode_and_decode_for_enum(
&mut self,
engines: &Engines,
decl: &TyDecl,
@ -249,16 +255,18 @@ where
let enum_decl_id = decl.to_enum_id(&Handler::default(), engines).unwrap();
let enum_decl = self.ctx.engines().de().get(&enum_decl_id);
let program_id = enum_decl.span().source_id().map(|sid| sid.program_id());
let abi_encode_body = self.generate_abi_encode_enum_body(engines, &enum_decl);
let abi_encode_code = self.generate_abi_encode_code(
enum_decl.name(),
&enum_decl.type_parameters,
abi_encode_body,
);
let abi_encode_node =
self.parse_impl_trait_to_ty_ast_node(engines, program_id, &abi_encode_code);
let abi_encode_node = self.parse_impl_trait_to_ty_ast_node(
engines,
enum_decl.span().source_id(),
&abi_encode_code,
crate::build_config::DbgGeneration::None,
);
let abi_decode_body = self.generate_abi_decode_enum_body(engines, &enum_decl);
let abi_decode_code = self.generate_abi_decode_code(
@ -266,8 +274,12 @@ where
&enum_decl.type_parameters,
abi_decode_body?,
);
let abi_decode_node =
self.parse_impl_trait_to_ty_ast_node(engines, program_id, &abi_decode_code);
let abi_decode_node = self.parse_impl_trait_to_ty_ast_node(
engines,
enum_decl.span().source_id(),
&abi_decode_code,
crate::build_config::DbgGeneration::None,
);
Some((abi_encode_node.ok(), abi_decode_node.ok()))
}
@ -278,8 +290,12 @@ where
decl: &ty::TyDecl,
) -> (Option<TyAstNode>, Option<TyAstNode>) {
match decl {
TyDecl::StructDecl(_) => self.auto_impl_struct(engines, decl).unwrap_or((None, None)),
TyDecl::EnumDecl(_) => self.auto_impl_enum(engines, decl).unwrap_or((None, None)),
TyDecl::StructDecl(_) => self
.auto_impl_abi_encode_and_decode_for_struct(engines, decl)
.unwrap_or((None, None)),
TyDecl::EnumDecl(_) => self
.auto_impl_abi_encode_and_decode_for_enum(engines, decl)
.unwrap_or((None, None)),
_ => (None, None),
}
}
@ -287,7 +303,7 @@ where
pub(crate) fn generate_contract_entry(
&mut self,
engines: &Engines,
program_id: Option<ProgramId>,
original_source_id: Option<&SourceId>,
contract_fns: &[DeclId<TyFunctionDecl>],
fallback_fn: Option<DeclId<TyFunctionDecl>>,
handler: &Handler,
@ -442,9 +458,10 @@ where
let entry_fn = self.parse_fn_to_ty_ast_node(
engines,
program_id,
original_source_id,
FunctionDeclarationKind::Entry,
&code,
crate::build_config::DbgGeneration::None,
);
match entry_fn {
@ -465,8 +482,6 @@ where
decl: &TyFunctionDecl,
handler: &Handler,
) -> Result<TyAstNode, ErrorEmitted> {
let program_id = decl.span.source_id().map(|sid| sid.program_id());
let Some(args_types) = decl
.parameters
.iter()
@ -503,9 +518,10 @@ where
let entry_fn = self.parse_fn_to_ty_ast_node(
engines,
program_id,
decl.span.source_id(),
FunctionDeclarationKind::Entry,
&code,
crate::build_config::DbgGeneration::None,
);
match entry_fn {
@ -558,8 +574,6 @@ where
decl: &TyFunctionDecl,
handler: &Handler,
) -> Result<TyAstNode, ErrorEmitted> {
let program_id = decl.span.source_id().map(|sid| sid.program_id());
let Some(args_types) = decl
.parameters
.iter()
@ -613,9 +627,10 @@ where
let entry_fn = self.parse_fn_to_ty_ast_node(
engines,
program_id,
decl.span.source_id(),
FunctionDeclarationKind::Entry,
&code,
crate::build_config::DbgGeneration::None,
);
match entry_fn {

View file

@ -0,0 +1,157 @@
use sway_error::handler::Handler;
use sway_types::{BaseIdent, Named, Spanned};
use crate::{
decl_engine::DeclEngineGet,
language::ty::{self, TyAstNode, TyDecl, TyEnumDecl, TyStructDecl},
Engines, TypeParameter,
};
#[derive(Default)]
pub struct DebugAutoImplInfo {}
pub type DebugAutoImplContext<'a, 'b> = super::AutoImplContext<'a, 'b, DebugAutoImplInfo>;
impl<'a, 'b> DebugAutoImplContext<'a, 'b>
where
'a: 'b,
{
pub fn generate_debug_impl(
&mut self,
engines: &Engines,
decl: &ty::TyDecl,
) -> Option<TyAstNode> {
match decl {
TyDecl::StructDecl(_) => self.auto_impl_debug_struct(engines, decl),
TyDecl::EnumDecl(_) => self.auto_impl_debug_enum(engines, decl),
_ => None,
}
}
// checks if the current module is a dependency of the `debug` module.
fn is_debug_dependency(&self) -> bool {
// Dependencies of the debug library in std cannot have debug implemented for them.
self.ctx.namespace.current_package_name().as_str() == "std"
&& matches!(
self.ctx.namespace.current_module().name().as_str(),
"codec"
| "raw_slice"
| "raw_ptr"
| "ops"
| "primitives"
| "registers"
| "flags"
| "debug"
)
}
// Auto implements Debug for structs and returns their `AstNode`s.
fn auto_impl_debug_struct(&mut self, engines: &Engines, decl: &TyDecl) -> Option<TyAstNode> {
if self.is_debug_dependency() {
return None;
}
let implementing_for_decl_id = decl.to_struct_decl(&Handler::default(), engines).unwrap();
let struct_decl = self.ctx.engines().de().get(&implementing_for_decl_id);
let body = self.generate_fmt_struct_body(engines, &struct_decl);
let code = self.generate_fmt_code(struct_decl.name(), &struct_decl.type_parameters, body);
let node = self.parse_impl_trait_to_ty_ast_node(
engines,
struct_decl.span().source_id(),
&code,
crate::build_config::DbgGeneration::None,
);
node.ok()
}
fn generate_fmt_code(
&self,
name: &BaseIdent,
type_parameters: &[TypeParameter],
body: String,
) -> String {
let type_parameters_declaration =
self.generate_type_parameters_declaration_code(type_parameters);
let type_parameters_constraints =
self.generate_type_parameters_constraints_code(type_parameters, "Debug");
let name = name.as_str();
format!("#[allow(dead_code, deprecated)] impl{type_parameters_declaration} Debug for {name}{type_parameters_declaration}{type_parameters_constraints} {{
#[allow(dead_code, deprecated)]
fn fmt(self, ref mut _f: Formatter) {{
{body}
}}
}}")
}
fn generate_fmt_struct_body(&self, _engines: &Engines, decl: &TyStructDecl) -> String {
let mut fields = String::new();
for field in decl.fields.iter() {
fields.push_str(&format!(
".field(\"{field_name}\", self.{field_name})\n",
field_name = field.name.as_str(),
));
}
format!(
"_f.debug_struct(\"{}\"){fields}.finish();",
decl.name().as_str()
)
}
// Auto implements Debug for enums and returns their `AstNode`s.
fn auto_impl_debug_enum(&mut self, engines: &Engines, decl: &TyDecl) -> Option<TyAstNode> {
if self.is_debug_dependency() {
return None;
}
let enum_decl_id = decl.to_enum_id(&Handler::default(), engines).unwrap();
let enum_decl = self.ctx.engines().de().get(&enum_decl_id);
let body = self.generate_fmt_enum_body(engines, &enum_decl);
let code = self.generate_fmt_code(enum_decl.name(), &enum_decl.type_parameters, body);
let node = self.parse_impl_trait_to_ty_ast_node(
engines,
enum_decl.span().source_id(),
&code,
crate::build_config::DbgGeneration::None,
);
node.ok()
}
fn generate_fmt_enum_body(&self, engines: &Engines, decl: &TyEnumDecl) -> String {
let enum_name = decl.call_path.suffix.as_str();
let arms = decl
.variants
.iter()
.map(|variant| {
let variant_name = variant.name.as_str();
if engines.te().get(variant.type_argument.type_id()).is_unit() {
format!(
"{enum_name}::{variant_name} => {{
_f.print_str(\"{variant_name}\");
}}, \n",
enum_name = enum_name,
variant_name = variant_name
)
} else {
format!(
"{enum_name}::{variant_name}(value) => {{
_f.debug_tuple(\"{enum_name}\").field(value).finish();
}}, \n",
enum_name = enum_name,
variant_name = variant_name,
)
}
})
.collect::<String>();
format!("match self {{ {arms} }};")
}
}

View file

@ -44,15 +44,17 @@ where
return None;
}
let program_id = enum_decl.span().source_id().map(|sid| sid.program_id());
let impl_enum_code = format!(
"#[allow(dead_code, deprecated)] impl {marker_trait_name} for {} {{ }}",
enum_decl.name()
);
let impl_enum_node =
self.parse_impl_trait_to_ty_ast_node(engines, program_id, &impl_enum_code);
let impl_enum_node = self.parse_impl_trait_to_ty_ast_node(
engines,
enum_decl.span().source_id(),
&impl_enum_code,
crate::build_config::DbgGeneration::None,
);
impl_enum_node.ok()
}

View file

@ -1,21 +1,23 @@
//! This module contains common infrastructure for generating and parsing auto-generated code.
pub mod abi_encoding;
pub mod debug;
pub mod marker_traits;
use std::ops::Deref;
use crate::{
build_config::DbgGeneration,
engine_threading::SpannedWithEngines,
language::{
parsed::{self, AstNodeContent, Declaration, FunctionDeclarationKind},
ty::{self, TyAstNode, TyDecl},
},
semantic_analysis::TypeCheckContext,
Engines, GenericArgument, TypeInfo, TypeParameter,
BuildTarget, Engines, GenericArgument, TypeInfo, TypeParameter,
};
use sway_error::handler::Handler;
use sway_parse::Parse;
use sway_types::{Named, ProgramId, Spanned};
use sway_types::{Named, SourceId, Spanned};
/// Contains all information needed to auto-implement code for a certain feature.
pub struct AutoImplContext<'a, 'b, I>
@ -45,19 +47,23 @@ where
/// Parses `input` into the expected [Parse] type.
/// The resulted [Parse] has source id set to autogenerated source id
/// within the program represented by the `program_id`.
fn parse<T>(engines: &Engines, program_id: Option<ProgramId>, input: &str) -> T
fn parse<T>(engines: &Engines, original_source_id: Option<&SourceId>, src: &str) -> T
where
T: Parse,
{
// Uncomment this to see what is being generated
// println!("{}", input);
// println!("{}", src);
let handler = <_>::default();
let source_id =
program_id.map(|program_id| engines.se().get_autogenerated_source_id(program_id));
let autogenerated_source_id = original_source_id.as_ref().and_then(|source_id| {
engines
.se()
.get_associated_autogenerated_source_id(source_id)
});
let ts = sway_parse::lex(&handler, input.into(), 0, input.len(), source_id).unwrap();
let mut p = sway_parse::Parser::new(&handler, &ts);
let token_stream =
sway_parse::lex(&handler, src.into(), 0, src.len(), autogenerated_source_id).unwrap();
let mut p = sway_parse::Parser::new(&handler, &token_stream);
p.check_double_underscore = false;
let r = p.parse();
@ -126,18 +132,20 @@ where
pub fn parse_fn_to_ty_ast_node(
&mut self,
engines: &Engines,
program_id: Option<ProgramId>,
original_source_id: Option<&SourceId>,
kind: FunctionDeclarationKind,
code: &str,
dbg_generation: DbgGeneration,
) -> Result<TyAstNode, Handler> {
let mut ctx = crate::transform::to_parsed_lang::Context::new(
crate::BuildTarget::Fuel,
dbg_generation,
self.ctx.experimental,
);
let handler = Handler::default();
let item = Self::parse(engines, program_id, code);
let item = Self::parse(engines, original_source_id, code);
let nodes = crate::transform::to_parsed_lang::item_to_ast_nodes(
&mut ctx,
&handler,
@ -157,12 +165,7 @@ where
panic!(
"{:?} {:?}",
handler,
program_id
.and_then(|x| engines.se().get_source_ids_from_program_id(x))
.unwrap()
.iter()
.map(|x| engines.se().get_file_name(x))
.collect::<Vec<_>>()
original_source_id.map(|x| engines.se().get_file_name(x))
);
}
assert!(!handler.has_warnings(), "{:?}", handler);
@ -206,17 +209,19 @@ where
fn parse_impl_trait_to_ty_ast_node(
&mut self,
engines: &Engines,
program_id: Option<ProgramId>,
original_source_id: Option<&SourceId>,
code: &str,
dbg_generation: DbgGeneration,
) -> Result<TyAstNode, Handler> {
let mut ctx = crate::transform::to_parsed_lang::Context::new(
crate::BuildTarget::Fuel,
BuildTarget::Fuel,
dbg_generation,
self.ctx.experimental,
);
let handler = Handler::default();
let item = Self::parse(engines, program_id, code);
let item = Self::parse(engines, original_source_id, code);
let nodes = crate::transform::to_parsed_lang::item_to_ast_nodes(
&mut ctx, &handler, engines, item, false, None,
)

View file

@ -110,6 +110,9 @@ impl ty::TyIntrinsicFunctionKind {
Intrinsic::Transmute => {
type_check_transmute(arguments, handler, kind, type_arguments, span, ctx)
}
Intrinsic::Dbg => {
unreachable!("__dbg should not exist in the typed tree")
}
}
}
}

View file

@ -677,7 +677,8 @@ fn effects_of_intrinsic(intr: &sway_ast::Intrinsic) -> HashSet<Effect> {
| EncodeBufferAsRawSlice
| Slice
| ElemAt
| Transmute => HashSet::new(),
| Transmute
| Dbg => HashSet::new(),
}
}

View file

@ -33,7 +33,8 @@ use crate::{
use super::{
declaration::auto_impl::{
abi_encoding::AbiEncodingAutoImplContext, marker_traits::MarkerTraitsAutoImplContext,
abi_encoding::AbiEncodingAutoImplContext, debug::DebugAutoImplContext,
marker_traits::MarkerTraitsAutoImplContext,
},
symbol_collection_context::SymbolCollectionContext,
};
@ -456,7 +457,7 @@ impl ty::TyModule {
let mut fn_generator = AbiEncodingAutoImplContext::new(&mut ctx);
if let Ok(node) = fn_generator.generate_contract_entry(
engines,
parsed.span.source_id().map(|x| x.program_id()),
parsed.span.source_id(),
&contract_fns,
fallback_fn,
handler,
@ -553,20 +554,29 @@ impl ty::TyModule {
let all_abi_encode_impls = Self::get_all_impls(ctx.by_ref(), nodes, |decl| {
decl.trait_name.suffix.as_str() == "AbiEncode"
});
let all_debug_impls = Self::get_all_impls(ctx.by_ref(), nodes, |decl| {
decl.trait_name.suffix.as_str() == "Debug"
});
let mut typed_nodes = vec![];
for node in nodes {
// Check if the encoding traits are explicitly implemented.
let auto_impl_encoding_traits = match &node.content {
// Check if the encoding and debug traits are explicitly implemented.
let (auto_impl_encoding_traits, auto_impl_debug_traits) = match &node.content {
AstNodeContent::Declaration(Declaration::StructDeclaration(decl_id)) => {
let decl = ctx.engines().pe().get_struct(decl_id);
!all_abi_encode_impls.contains_key(&decl.name)
(
!all_abi_encode_impls.contains_key(&decl.name),
!all_debug_impls.contains_key(&decl.name),
)
}
AstNodeContent::Declaration(Declaration::EnumDeclaration(decl_id)) => {
let decl = ctx.engines().pe().get_enum(decl_id);
!all_abi_encode_impls.contains_key(&decl.name)
(
!all_abi_encode_impls.contains_key(&decl.name),
!all_debug_impls.contains_key(&decl.name),
)
}
_ => false,
_ => (false, false),
};
let Ok(node) = ty::TyAstNode::type_check(handler, ctx.by_ref(), node) else {
@ -593,6 +603,19 @@ impl ty::TyModule {
};
}
// Auto impl debug traits only if they are not explicitly implemented
if auto_impl_debug_traits {
match &node.content {
TyAstNodeContent::Declaration(decl @ TyDecl::StructDecl(_))
| TyAstNodeContent::Declaration(decl @ TyDecl::EnumDecl(_)) => {
let mut ctx = DebugAutoImplContext::new(&mut ctx);
let a = ctx.generate_debug_impl(engines, decl);
generated.extend(a);
}
_ => {}
}
}
// Always auto impl marker traits. If an explicit implementation exists, that will be
// reported as an error when type-checking trait impls.
if ctx.experimental.error_type {

View file

@ -7,6 +7,7 @@ use sway_parse::{lex, Parser};
use sway_types::{constants::CONTRACT_ID, ProgramId, Spanned};
use crate::{
build_config::DbgGeneration,
language::{
parsed::{AstNode, AstNodeContent, Declaration, ExpressionKind},
ty::{TyAstNode, TyAstNodeContent},
@ -25,17 +26,25 @@ pub fn package_with_contract_id(
program_id: ProgramId,
contract_id_value: String,
experimental: crate::ExperimentalFeatures,
dbg_generation: DbgGeneration,
) -> Result<Package, vec1::Vec1<CompileError>> {
let package = Package::new(package_name, None, program_id, true);
let handler = <_>::default();
bind_contract_id_in_root_module(&handler, engines, contract_id_value, package, experimental)
.map_err(|_| {
let (errors, warnings) = handler.consume();
assert!(warnings.is_empty());
bind_contract_id_in_root_module(
&handler,
engines,
contract_id_value,
package,
experimental,
dbg_generation,
)
.map_err(|_| {
let (errors, warnings) = handler.consume();
assert!(warnings.is_empty());
// Invariant: `.value == None` => `!errors.is_empty()`.
vec1::Vec1::try_from_vec(errors).unwrap()
})
// Invariant: `.value == None` => `!errors.is_empty()`.
vec1::Vec1::try_from_vec(errors).unwrap()
})
}
fn bind_contract_id_in_root_module(
@ -44,6 +53,7 @@ fn bind_contract_id_in_root_module(
contract_id_value: String,
package: Package,
experimental: crate::ExperimentalFeatures,
dbg_generation: DbgGeneration,
) -> Result<Package, ErrorEmitted> {
// this for loop performs a miniature compilation of each const item in the config
// FIXME(Centril): Stop parsing. Construct AST directly instead!
@ -61,7 +71,7 @@ fn bind_contract_id_in_root_module(
let attributes = Default::default();
// convert to const decl
let const_decl_id = to_parsed_lang::item_const_to_constant_declaration(
&mut to_parsed_lang::Context::new(crate::BuildTarget::EVM, experimental),
&mut to_parsed_lang::Context::new(crate::BuildTarget::EVM, dbg_generation, experimental),
handler,
engines,
const_item,

View file

@ -1,6 +1,7 @@
use sway_features::ExperimentalFeatures;
use crate::{
build_config::DbgGeneration,
language::parsed::{Declaration, TreeType},
BuildTarget,
};
@ -27,6 +28,9 @@ pub struct Context {
/// The build target.
build_target: BuildTarget,
/// Indicates whether the `__dbg` intrinsic generates code or not
dbg_generation: DbgGeneration,
/// The program type.
program_type: Option<TreeType>,
@ -36,9 +40,14 @@ pub struct Context {
impl Context {
/// Create a new context.
pub fn new(build_target: BuildTarget, experimental: ExperimentalFeatures) -> Self {
pub fn new(
build_target: BuildTarget,
dbg_generation: DbgGeneration,
experimental: ExperimentalFeatures,
) -> Self {
Self {
build_target,
dbg_generation,
experimental,
module_has_configurable_block: std::default::Default::default(),
destructured_struct_unique_suffix: std::default::Default::default(),
@ -90,6 +99,10 @@ impl Context {
self.build_target
}
pub fn is_dbg_generation_full(&self) -> bool {
matches!(self.dbg_generation, DbgGeneration::Full)
}
/// Returns the program type.
pub fn program_type(&self) -> Option<TreeType> {
self.program_type

View file

@ -2009,8 +2009,9 @@ fn expr_func_app_to_expression_kind(
let (type_arguments, type_arguments_span) = convert_ty_args(context, call_seg.generics_opt)?;
// Route intrinsic calls to different AST node.
// Transform the AST of some intrinsics
match Intrinsic::try_from_str(call_seg.name.as_str()) {
// "__log(arg)" becomes "__log(encode(arg))"
Some(Intrinsic::Log)
if context.experimental.new_encoding && last.is_none() && !is_relative_to_root =>
{
@ -2044,6 +2045,214 @@ fn expr_func_app_to_expression_kind(
},
));
}
// "__dbg(arg)" in debug becomes "{
// let mut f = Formatter { };
// f.print_str("[{current_file}:{current_line}:{current_col}] = ");
// let arg = arg;
// arg.fmt(f);
// arg
// }"
Some(Intrinsic::Dbg)
if context.is_dbg_generation_full() && last.is_none() && !is_relative_to_root =>
{
if arguments.len() != 1 {
return Err(handler.emit_err(CompileError::IntrinsicIncorrectNumArgs {
name: Intrinsic::Dbg.to_string(),
expected: 1,
span,
}));
}
let f_id: String = format!("f_{}", context.next_for_unique_suffix());
let f_ident = BaseIdent::new_no_span(f_id.to_string());
let f_tid = engines.te().new_unknown();
let f_decl_pid = engines.pe().insert(VariableDeclaration {
name: f_ident.clone(),
type_ascription: GenericArgument::Type(GenericTypeArgument {
type_id: f_tid,
initial_type_id: f_tid,
span: span.clone(),
call_path_tree: None,
}),
body: Expression {
kind: ExpressionKind::Struct(Box::new(StructExpression {
resolved_call_path_binding: None,
call_path_binding: TypeBinding {
inner: CallPath {
prefixes: vec![],
suffix: BaseIdent::new_no_span("Formatter".into()),
callpath_type: CallPathType::Ambiguous,
},
type_arguments: TypeArgs::Regular(vec![]),
span: span.clone(),
},
fields: vec![],
})),
span: span.clone(),
},
is_mutable: true,
});
fn get_current_file_from_span(engines: &Engines, span: &Span) -> String {
let Some(source_id) = span.source_id() else {
return String::new();
};
let current_file = engines.se().get_path(source_id);
// find the manifest path of the current span
let program_id = engines
.se()
.get_program_id_from_manifest_path(&current_file)
.unwrap();
let manifest_path = engines
.se()
.get_manifest_path_from_program_id(&program_id)
.unwrap();
let current_file = current_file
.display()
.to_string()
.replace(&manifest_path.display().to_string(), "");
if let Some(current_file) = current_file.strip_prefix("/") {
current_file.to_string()
} else {
current_file
}
}
fn ast_node_to_print_str(f_ident: BaseIdent, s: &str, span: &Span) -> AstNode {
AstNode {
content: AstNodeContent::Expression(Expression {
kind: ExpressionKind::MethodApplication(Box::new(
MethodApplicationExpression {
method_name_binding: TypeBinding {
inner: MethodName::FromModule {
method_name: BaseIdent::new_no_span("print_str".into()),
},
type_arguments: TypeArgs::Regular(vec![]),
span: span.clone(),
},
contract_call_params: vec![],
arguments: vec![
Expression {
kind: ExpressionKind::Variable(f_ident.clone()),
span: span.clone(),
},
Expression {
kind: ExpressionKind::Literal(Literal::String(
Span::from_string(s.to_string()),
)),
span: span.clone(),
},
],
},
)),
span: span.clone(),
}),
span: span.clone(),
}
}
let current_file = get_current_file_from_span(engines, &span);
let start_line_col = span.start_line_col_one_index();
let arg_id: String = format!("arg_{}", context.next_for_unique_suffix());
let arg_ident = BaseIdent::new_no_span(arg_id.to_string());
let block = CodeBlock {
contents: vec![
// let arg = arguments[0];
statement_let_to_ast_nodes_unfold(
context,
handler,
engines,
Pattern::AmbiguousSingleIdent(arg_ident.clone()),
None,
arguments[0].clone(),
Span::dummy(),
)
.unwrap()
.pop()
.unwrap(),
// let mut f = Formatter { };
AstNode {
content: AstNodeContent::Declaration(Declaration::VariableDeclaration(
f_decl_pid,
)),
span: Span::dummy(),
},
// f.print_str("[" + <current file> + ":" + <current line> + ":" + <current col> + "] = ");
ast_node_to_print_str(
f_ident.clone(),
&format!(
"[{}:{}:{}] = ",
current_file, start_line_col.line, start_line_col.col
),
&span,
),
// arg.fmt(f);
AstNode {
content: AstNodeContent::Expression(Expression {
kind: ExpressionKind::MethodApplication(Box::new(
MethodApplicationExpression {
method_name_binding: TypeBinding {
inner: MethodName::FromModule {
method_name: BaseIdent::new_no_span("fmt".into()),
},
type_arguments: TypeArgs::Regular(vec![]),
span: Span::dummy(),
},
contract_call_params: vec![],
arguments: vec![
Expression {
kind: ExpressionKind::Variable(arg_ident.clone()),
span: Span::dummy(),
},
Expression {
kind: ExpressionKind::Variable(f_ident.clone()),
span: Span::dummy(),
},
],
},
)),
span: Span::dummy(),
}),
span: Span::dummy(),
},
// f.print_str(<newline>);
ast_node_to_print_str(f_ident.clone(), "\n", &span),
// arg
AstNode {
content: AstNodeContent::Expression(Expression {
kind: ExpressionKind::ImplicitReturn(Box::new(Expression {
kind: ExpressionKind::AmbiguousVariableExpression(
arg_ident.clone(),
),
span: Span::dummy(),
})),
span: Span::dummy(),
}),
span: Span::dummy(),
},
],
whole_block_span: Span::dummy(),
};
return Ok(ExpressionKind::CodeBlock(block));
}
// ... and in release becomes "arg"
Some(Intrinsic::Dbg)
if !context.is_dbg_generation_full() && last.is_none() && !is_relative_to_root =>
{
if arguments.len() != 1 {
return Err(handler.emit_err(CompileError::IntrinsicIncorrectNumArgs {
name: Intrinsic::Dbg.to_string(),
expected: 1,
span,
}));
}
return Ok(arguments[0].kind.clone());
}
Some(intrinsic) if last.is_none() && !is_relative_to_root => {
return Ok(ExpressionKind::IntrinsicFunction(
IntrinsicFunctionExpression {

View file

@ -917,7 +917,7 @@ pub enum CompileError {
span: Span,
hint: String,
},
#[error("Call to \"{name}\" expects {expected} arguments")]
#[error("Call to \"{name}\" expects {expected} argument(s)")]
IntrinsicIncorrectNumArgs {
name: String,
expected: u64,

View file

@ -2,14 +2,14 @@
# Needs to exist at least one line between them
remove_generated_code() {
START=`grep -n "BEGIN $1" ./src/codec.sw`
START=`grep -n "BEGIN $1" ./src/$2`
START=${START%:*}
END=`grep -n "END $1" ./src/codec.sw`
END=`grep -n "END $1" ./src/$2`
END=${END%:*}
sed -i "$((START+1)),$((END-1))d" ./src/codec.sw
sed -i "$((START+1)),$((END-1))d" ./src/$2
}
remove_generated_code "ARRAY_ENCODE"
remove_generated_code "ARRAY_ENCODE" "codec.sw"
START=1
END=64
for ((i=END;i>=START;i--)); do
@ -17,7 +17,7 @@ for ((i=END;i>=START;i--)); do
sed -i "s/\/\/ BEGIN ARRAY_ENCODE/\/\/ BEGIN ARRAY_ENCODE\n$CODE/g" ./src/codec.sw
done
remove_generated_code "ARRAY_DECODE"
remove_generated_code "ARRAY_DECODE" "codec.sw"
START=1
END=64
for ((i=END;i>=START;i--)); do
@ -25,7 +25,7 @@ for ((i=END;i>=START;i--)); do
sed -i "s/\/\/ BEGIN ARRAY_DECODE/\/\/ BEGIN ARRAY_DECODE\n$CODE/g" ./src/codec.sw
done
remove_generated_code "STRARRAY_ENCODE"
remove_generated_code "STRARRAY_ENCODE" "codec.sw"
START=1
END=64
for ((i=END;i>=START;i--)); do
@ -33,7 +33,7 @@ for ((i=END;i>=START;i--)); do
sed -i "s/\/\/ BEGIN STRARRAY_ENCODE/\/\/ BEGIN STRARRAY_ENCODE\n$CODE/g" ./src/codec.sw
done
remove_generated_code "STRARRAY_DECODE"
remove_generated_code "STRARRAY_DECODE" "codec.sw"
START=1
END=64
for ((i=END;i>=START;i--)); do
@ -78,7 +78,7 @@ generate_tuple_encode() {
sed -i "s/\/\/ BEGIN TUPLES_ENCODE/\/\/ BEGIN TUPLES_ENCODE\n$CODE/g" ./src/codec.sw
}
remove_generated_code "TUPLES_ENCODE"
remove_generated_code "TUPLES_ENCODE" "codec.sw"
generate_tuple_encode "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
generate_tuple_encode "A B C D E F G H I J K L M N O P Q R S T U V W X Y"
generate_tuple_encode "A B C D E F G H I J K L M N O P Q R S T U V W X"
@ -141,7 +141,7 @@ generate_tuple_decode() {
sed -i "s/\/\/ BEGIN TUPLES_DECODE/\/\/ BEGIN TUPLES_DECODE\n$CODE/g" ./src/codec.sw
}
remove_generated_code "TUPLES_DECODE"
remove_generated_code "TUPLES_DECODE" "codec.sw"
generate_tuple_decode "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
generate_tuple_decode "A B C D E F G H I J K L M N O P Q R S T U V W X Y"
generate_tuple_decode "A B C D E F G H I J K L M N O P Q R S T U V W X"
@ -168,3 +168,86 @@ generate_tuple_decode "A B C D"
generate_tuple_decode "A B C"
generate_tuple_decode "A B"
generate_tuple_decode "A"
generate_tuple_debug() {
local CODE="impl<"
local elements=("$1")
for element in ${elements[@]}
do
CODE="$CODE $element,"
done
CODE="$CODE> Debug for ("
for element in ${elements[@]}
do
CODE="$CODE $element,"
done
CODE="$CODE) where "
for element in ${elements[@]}
do
CODE="$CODE $element: Debug, "
done
CODE="$CODE{ fn fmt(self, ref mut f: Formatter) { let mut f = f.debug_tuple(\"\");"
i=0
for element in ${elements[@]}
do
CODE="$CODE let mut f = f.field(self.$i);"
i=$((i+1))
done
CODE="$CODE f.finish(); } }"
sed -i "s/\/\/ BEGIN TUPLES_DEBUG/\/\/ BEGIN TUPLES_DEBUG\n$CODE/g" ./src/debug.sw
}
remove_generated_code "TUPLES_DEBUG" "debug.sw"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R S T U V W X Y"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R S T U V W X"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R S T U V W"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R S T U V"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R S T U"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R S T"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R S"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q R"
generate_tuple_debug "A B C D E F G H I J K L M N O P Q"
generate_tuple_debug "A B C D E F G H I J K L M N O P"
generate_tuple_debug "A B C D E F G H I J K L M N O"
generate_tuple_debug "A B C D E F G H I J K L M N"
generate_tuple_debug "A B C D E F G H I J K L M"
generate_tuple_debug "A B C D E F G H I J K L"
generate_tuple_debug "A B C D E F G H I J K"
generate_tuple_debug "A B C D E F G H I J"
generate_tuple_debug "A B C D E F G H I"
generate_tuple_debug "A B C D E F G H"
generate_tuple_debug "A B C D E F G"
generate_tuple_debug "A B C D E F"
generate_tuple_debug "A B C D E"
generate_tuple_debug "A B C D"
generate_tuple_debug "A B C"
generate_tuple_debug "A B"
generate_tuple_debug "A"
remove_generated_code "STRARRAY_DEBUG" "debug.sw"
START=1
END=64
for ((i=END;i>=START;i--)); do
CODE="impl Debug for str[$i] { fn fmt(self, ref mut f: Formatter) { use ::str::*; from_str_array(self).fmt(f); } }"
sed -i "s/\/\/ BEGIN STRARRAY_DEBUG/\/\/ BEGIN STRARRAY_DEBUG\n$CODE/g" ./src/debug.sw
done
remove_generated_code "ARRAY_DEBUG" "debug.sw"
START=1
END=64
for ((i=END;i>=START;i--)); do
CODE="#[cfg(experimental_const_generics = false)]\nimpl<T> Debug for [T; $i] where T: Debug { fn fmt(self, ref mut f: Formatter) { let mut f = f.debug_list(); let mut i = 0; while i < $i { f = f.entry(self[i]); i += 1; }; f.finish(); } }"
sed -i "s/\/\/ BEGIN ARRAY_DEBUG/\/\/ BEGIN ARRAY_DEBUG\n$CODE/g" ./src/debug.sw
done
cargo r -p forc-fmt --release -- -p .

2576
sway-lib-std/src/debug.sw Normal file

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,7 @@ pub mod ops;
pub mod raw_ptr;
pub mod raw_slice;
pub mod codec;
pub mod debug;
pub mod slice;
pub mod constants;
pub mod error_signals;

View file

@ -50,3 +50,4 @@ pub use ::codec::*;
pub use ::str::*;
#[cfg(experimental_error_type = true)]
pub use ::marker::*;
pub use ::debug::*;

View file

@ -10,6 +10,7 @@ use ::codec::*;
use ::ops::*;
use ::raw_slice::*;
use ::clone::Clone;
use ::debug::{Debug, DebugList, Formatter};
struct RawVec<T> {
ptr: raw_ptr,
@ -905,3 +906,18 @@ where
true
}
}
impl<T> Debug for Vec<T>
where
T: Debug,
{
fn fmt(self, ref mut f: Formatter) {
let mut l = f.debug_list();
for elem in self.iter() {
let _ = l.entry(elem);
}
l.finish();
}
}

View file

@ -150,6 +150,7 @@ impl Session {
let path = uri.to_file_path().unwrap();
let source_id = { engines.se().get_source_id(&path) };
engines.clear_module(&source_id);
Ok(())
}
@ -326,6 +327,7 @@ pub fn compile(
retrigger_compilation,
&[],
&[sway_features::Feature::NewEncoding],
sway_core::DbgGeneration::None,
)
.map_err(LanguageServerError::FailedToCompile)
}

View file

@ -17,6 +17,12 @@ struct MyStruct<T, U> {
o: Option<Identity>,
}
impl<T, U> Debug for MyStruct<T, U> {
fn fmt(self, f: Formatter) {
}
}
struct Simple {
x: u8,
}

View file

@ -1239,6 +1239,7 @@ fn go_to_definition_for_functions() {
}
#[test]
#[ignore = "https://github.com/FuelLabs/sway/issues/7025"]
fn go_to_definition_for_structs() {
run_async!({
let server = ServerState::default();

View file

@ -3,6 +3,7 @@ use parking_lot::RwLock;
use std::{
collections::{BTreeSet, HashMap},
path::PathBuf,
str::FromStr,
};
/// The Source Engine manages a relationship between file paths and their corresponding
@ -47,7 +48,10 @@ impl SourceEngine {
}
pub fn is_source_id_autogenerated(&self, source_id: &SourceId) -> bool {
self.get_path(source_id).starts_with("<autogenerated>")
self.get_path(source_id)
.display()
.to_string()
.contains("<autogenerated>")
}
/// This function retrieves an integer-based source ID for a provided path buffer.
@ -91,8 +95,20 @@ impl SourceEngine {
source_id
}
pub fn get_autogenerated_source_id(&self, program_id: ProgramId) -> SourceId {
self.get_source_id_with_program_id(&Self::AUTOGENERATED_PATH.into(), program_id)
/// Return the associated autogenerated pseudo file for the passed `source_id`.
/// Example: main.autogenerated.sw for main.sw
///
/// Returns `None`, if `source_id` does not have a valid path.
pub fn get_associated_autogenerated_source_id(&self, source_id: &SourceId) -> Option<SourceId> {
let path = self.get_path(source_id);
let file_name = PathBuf::from_str(path.file_name()?.to_str()?).ok()?;
let path = path.with_file_name(format!(
"{}.{}.{}",
file_name.file_stem()?.to_str()?,
Self::AUTOGENERATED_PATH,
file_name.extension()?.to_str()?
));
Some(self.get_source_id_with_program_id(&path, source_id.program_id()))
}
/// This function provides the file path corresponding to a specified source ID.

View file

@ -7,11 +7,12 @@ use forc_client::{
NodeTarget,
};
use forc_pkg::{BuildProfile, Built, BuiltPackage, PrintOpts};
use forc_test::ecal::EcalSyscallHandler;
use fuel_tx::TransactionBuilder;
use fuel_vm::checked_transaction::builder::TransactionBuilderExt;
use fuel_vm::fuel_tx::{self, consensus_parameters::ConsensusParametersV1};
use fuel_vm::interpreter::Interpreter;
use fuel_vm::prelude::*;
use fuel_vm::{checked_transaction::builder::TransactionBuilderExt, interpreter::NotSupportedEcal};
use futures::Future;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
@ -143,7 +144,7 @@ pub(crate) async fn runs_on_node(
}
pub(crate) enum VMExecutionResult {
Fuel(ProgramState, Vec<Receipt>),
Fuel(ProgramState, Vec<Receipt>, Box<EcalSyscallHandler>),
Evm(revm::primitives::result::ExecutionResult),
}
@ -207,13 +208,14 @@ pub(crate) fn runs_in_vm(
.map_err(|e| anyhow::anyhow!("{e:?}"))?;
let mem_instance = MemoryInstance::new();
let mut i: Interpreter<_, _, _, NotSupportedEcal> =
let mut i: Interpreter<_, _, _, EcalSyscallHandler> =
Interpreter::with_storage(mem_instance, storage, Default::default());
let transition = i.transact(tx).map_err(anyhow::Error::msg)?;
Ok(VMExecutionResult::Fuel(
*transition.state(),
transition.receipts().to_vec(),
Box::new(i.ecal_state().clone()),
))
}
BuildTarget::EVM => {

View file

@ -11,6 +11,7 @@ use colored::*;
use core::fmt;
use forc_pkg::manifest::{GenericManifestFile, ManifestFile};
use forc_pkg::BuildProfile;
use forc_test::ecal::Syscall;
use forc_util::tx_utils::decode_log_data;
use fuel_vm::fuel_tx;
use fuel_vm::fuel_types::canonical::Input;
@ -404,8 +405,27 @@ impl TestContext {
let result = harness::runs_in_vm(compiled.clone(), script_data, witness_data)?;
let actual_result = match result {
harness::VMExecutionResult::Fuel(state, receipts) => {
harness::VMExecutionResult::Fuel(state, receipts, ecal) => {
print_receipts(output, &receipts);
use std::fmt::Write;
let _ = writeln!(output, " {}", "Captured Output".green().bold());
for captured in ecal.captured.iter() {
match captured {
Syscall::Write { bytes, .. } => {
let s = std::str::from_utf8(bytes.as_slice()).unwrap();
output.push_str(s);
}
Syscall::Unknown { ra, rb, rc, rd } => {
let _ = writeln!(
output,
"Unknown ecal: {} {} {} {}",
ra, rb, rc, rd
);
}
}
}
match state {
ProgramState::Return(v) => TestResult::Return(v),
ProgramState::ReturnData(digest) => {

View file

@ -13,3 +13,4 @@ assert.sw
error_signals.sw
logging.sw
revert.sw
debug.sw

View file

@ -16,3 +16,4 @@ pub mod logging;
pub mod revert;
pub mod assert;
pub mod prelude;
pub mod debug;

View file

@ -45,4 +45,5 @@ primitive_conversions/u16.sw
primitive_conversions/u32.sw
primitive_conversions/u64.sw
primitive_conversions/u256.sw
primitive_conversions/u8.sw
primitive_conversions/u8.sw
debug.sw

View file

@ -31,5 +31,6 @@ pub mod math;
pub mod array_conversions;
pub mod bytes_conversions;
pub mod clone;
pub mod debug;
pub mod prelude;

View file

@ -9,3 +9,4 @@ raw_slice.sw
codec.sw
str.sw
marker.sw
debug.sw

View file

@ -11,5 +11,5 @@ pub mod raw_slice;
pub mod codec;
pub mod r#str;
pub mod marker;
pub mod prelude;
pub mod debug;

View file

@ -14,3 +14,4 @@ pub use ::codec::*;
pub use ::str::*;
#[cfg(experimental_error_type = true)]
pub use ::marker::*;
pub use ::debug::*;

View file

@ -15,3 +15,4 @@ logging.sw
option.sw
result.sw
revert.sw
debug.sw

View file

@ -18,3 +18,4 @@ pub mod result;
pub mod option;
pub mod assert;
pub mod prelude;
pub mod debug;

View file

@ -20,3 +20,4 @@ vec.sw
iterator.sw
convert.sw
clone.sw
debug.sw

View file

@ -22,5 +22,6 @@ pub mod alloc;
pub mod iterator;
pub mod clone;
pub mod vec;
pub mod debug;
pub mod prelude;

View file

@ -0,0 +1,8 @@
[[package]]
name = "dbg_wrong_args_count"
source = "member"
dependencies = ["std"]
[[package]]
name = "std"
source = "path+from-root-7E04A7BB9FC18A6D"

View file

@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "dbg_wrong_args_count"
[dependencies]
std = { path = "../../../reduced_std_libs/sway-lib-std-core" }

View file

@ -0,0 +1,6 @@
script;
fn main() {
let _ = __dbg();
let _ = __dbg(1, 2);
}

View file

@ -0,0 +1,34 @@
---
source: test/src/snapshot/mod.rs
---
> forc build --path test/src/e2e_vm_tests/test_programs/should_fail/dbg_wrong_args_count
exit status: 1
output:
Building test/src/e2e_vm_tests/test_programs/should_fail/dbg_wrong_args_count
Compiling library std (test/src/e2e_vm_tests/reduced_std_libs/sway-lib-std-core)
Compiling script dbg_wrong_args_count (test/src/e2e_vm_tests/test_programs/should_fail/dbg_wrong_args_count)
error
--> test/src/e2e_vm_tests/test_programs/should_fail/dbg_wrong_args_count/src/main.sw:4:13
|
2 |
3 | fn main() {
4 | let _ = __dbg();
| ^^^^^^^ Call to "dbg" expects 1 argument(s)
5 | let _ = __dbg(1, 2);
6 | }
|
____
error
--> test/src/e2e_vm_tests/test_programs/should_fail/dbg_wrong_args_count/src/main.sw:5:13
|
3 | fn main() {
4 | let _ = __dbg();
5 | let _ = __dbg(1, 2);
| ^^^^^^^^^^^ Call to "dbg" expects 1 argument(s)
6 | }
|
____
Aborting due to 2 errors.
error: Failed to compile dbg_wrong_args_count

View file

@ -4,4 +4,4 @@ category = "fail"
# check: $()buffer: __encode_buffer_append(buffer.buffer)
# check: $()buffer: __encode_buffer_append(buffer.buffer)
# nextln: $()Call to "encode_buffer_append" expects 2 arguments
# nextln: $()Call to "encode_buffer_append" expects 2 argument(s)

View file

@ -1,4 +1,4 @@
category = "fail"
# check: $()buffer: __encode_buffer_empty(self),
# nextln: $()Call to "encode_buffer_empty" expects 0 arguments
# nextln: $()Call to "encode_buffer_empty" expects 0 argument(s)

View file

@ -1,6 +1,5 @@
---
source: test/src/snapshot/mod.rs
assertion_line: 125
---
> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_deprecated --release
exit status: 0
@ -165,7 +164,7 @@ warning: Struct field is deprecated
____
warning: Function is deprecated
--> <autogenerated>:5:15
--> test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_deprecated/src/main.<autogenerated>.sw:5:15
|
...
5 | let _result = __contract_entry_deprecated_to_be_abi_method();
@ -174,7 +173,7 @@ warning: Function is deprecated
____
warning: Function is deprecated
--> <autogenerated>:9:15
--> test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_deprecated/src/main.<autogenerated>.sw:9:15
|
...
9 | let _result = __contract_entry_deprecated_abi_provided_method();

View file

@ -271,7 +271,7 @@ script {
!39 = span !3 2804 2817
!40 = span !3 2857 2896
!41 = (!32 !33 !40)
!42 = "<autogenerated>"
!42 = "test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/src/main.<autogenerated>.sw"
!43 = span !42 0 125
!44 = fn_name_span !42 7 14
!45 = (!43 !44)

View file

@ -0,0 +1,261 @@
---
source: test/src/snapshot/mod.rs
---
> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated
exit status: 0
output:
Building test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated
Compiling library std (test/src/e2e_vm_tests/reduced_std_libs/sway-lib-std-assert)
Compiling script deprecated (test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated)
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.<autogenerated>.sw:4:39
|
2 | #[allow(dead_code)]
3 | fn abi_encode(self, buffer: Buffer) -> Buffer {
4 | let buffer = self.a.abi_encode(buffer);
| - deprecated struct field
5 | let buffer = self.b.abi_encode(buffer);
6 |
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.<autogenerated>.sw:4:21
|
2 | #[allow(dead_code)]
3 | fn abi_decode(ref mut buffer: BufferReader) -> Self {
4 | Self { a: buffer.decode::<u64>(),b: buffer.decode::<u64>(), }
| ---- deprecated struct
5 | }
6 | }
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.<autogenerated>.sw:5:25
|
3 | fn abi_decode(ref mut buffer: BufferReader) -> Self {
4 | let variant: u64 = buffer.decode::<u64>();
5 | match variant { 0 => B::A,
| - deprecated enum
6 | 1 => B::B,
7 | _ => __revert(0), }
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.<autogenerated>.sw:6:9
|
4 | let variant: u64 = buffer.decode::<u64>();
5 | match variant { 0 => B::A,
6 | 1 => B::B,
| - deprecated enum
7 | _ => __revert(0), }
8 |
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.<autogenerated>.sw:6:9
|
4 | let variant: u64 = buffer.decode::<u64>();
5 | match variant { 0 => B::A,
6 | 1 => B::B,
| - deprecated enum variant
7 | _ => __revert(0), }
8 |
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:33:17
|
31 | // TODO: support for traits, abis and their methods
32 | pub fn main() {
33 | let mut a = A { a: 0, b: 0 };
| - deprecated struct
34 | let mut a_ref = &a;
35 | (*a_ref).a = 1;
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:35:14
|
33 | let mut a = A { a: 0, b: 0 };
34 | let mut a_ref = &a;
35 | (*a_ref).a = 1;
| - deprecated struct field
36 | let b = B::A;
37 | depr(a);
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:36:16
|
34 | let mut a_ref = &a;
35 | (*a_ref).a = 1;
36 | let b = B::A;
| - deprecated enum
37 | depr(a);
38 | depr(A { a: 0, b: 0 });
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:37:5
|
35 | (*a_ref).a = 1;
36 | let b = B::A;
37 | depr(a);
| ---- deprecated function
38 | depr(A { a: 0, b: 0 });
39 | depr_b(b);
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:38:10
|
36 | let b = B::A;
37 | depr(a);
38 | depr(A { a: 0, b: 0 });
| - deprecated struct
39 | depr_b(b);
40 | depr_b(B::A);
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:38:5
|
36 | let b = B::A;
37 | depr(a);
38 | depr(A { a: 0, b: 0 });
| ---- deprecated function
39 | depr_b(b);
40 | depr_b(B::A);
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:39:5
|
37 | depr(a);
38 | depr(A { a: 0, b: 0 });
39 | depr_b(b);
| ------ deprecated function
40 | depr_b(B::A);
41 | fun(a);
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:40:15
|
38 | depr(A { a: 0, b: 0 });
39 | depr_b(b);
40 | depr_b(B::A);
| - deprecated enum
41 | fun(a);
42 | fun(A { a: 0, b: 0 });
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:40:5
|
38 | depr(A { a: 0, b: 0 });
39 | depr_b(b);
40 | depr_b(B::A);
| ------ deprecated function
41 | fun(a);
42 | fun(A { a: 0, b: 0 });
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:42:9
|
40 | depr_b(B::A);
41 | fun(a);
42 | fun(A { a: 0, b: 0 });
| - deprecated struct
43 | let _ = a.a;
44 | let _ = a.b;
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:43:15
|
41 | fun(a);
42 | fun(A { a: 0, b: 0 });
43 | let _ = a.a;
| - deprecated struct field
44 | let _ = a.b;
45 | let _ = B::A;
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:45:16
|
43 | let _ = a.a;
44 | let _ = a.b;
45 | let _ = B::A;
| - deprecated enum
46 | let _ = B::B;
47 | a.fun();
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:46:16
|
44 | let _ = a.b;
45 | let _ = B::A;
46 | let _ = B::B;
| - deprecated enum
47 | a.fun();
48 | }
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:46:16
|
44 | let _ = a.b;
45 | let _ = B::A;
46 | let _ = B::B;
| - deprecated enum variant
47 | a.fun();
48 | }
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:47:7
|
45 | let _ = B::A;
46 | let _ = B::B;
47 | a.fun();
| --- deprecated struct
48 | }
|
____
warning
--> test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated/src/main.sw:47:7
|
45 | let _ = B::A;
46 | let _ = B::B;
47 | a.fun();
| --- deprecated function
48 | }
|
____
Compiled script "deprecated" with 21 warnings.
Finished debug [unoptimized + fuel] target(s) [312 B] in ???

View file

@ -0,0 +1,8 @@
[[package]]
name = "dbg"
source = "member"
dependencies = ["std"]
[[package]]
name = "std"
source = "path+from-root-2AFF83EAEE091763"

View file

@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
entry = "main.sw"
name = "dbg"
[dependencies]
std = { path = "../../../../../reduced_std_libs/sway-lib-std-core" }

View file

@ -0,0 +1,4 @@
cmds = [
"forc build --path {root} --asm final | grep ecal",
"forc build --path {root} --release --asm final | grep ecal"
]

View file

@ -0,0 +1,29 @@
script;
struct S {}
enum E { None: (), Some: S }
fn main() -> u64 {
// Primitive types
let _ = __dbg(8u8);
let _ = __dbg(16u16);
let _ = __dbg(32u32);
let _ = __dbg(64u64);
let _ = __dbg(0x100u256);
// strings
let _ = __dbg("Hello!");
let _ = __dbg(__to_str_array("Hello!"));
// Aggregates
let _ = __dbg((1u64, 2u64));
let _ = __dbg([1u64, 2u64]);
// Strucs and Enum
let _ = __dbg(S { });
let _ = __dbg(E::None);
let _ = __dbg(E::Some(S { }));
// should return its argument
__dbg(11u64)
}

View file

@ -0,0 +1,12 @@
---
source: test/src/snapshot/mod.rs
---
> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/intrinsics/dbg --asm final | grep ecal
ecal $r2 $r3 $r0 $r1 ; ecal id fd buf count
ecal $r3 $r0 $r1 $r2 ; ecal id fd buf count
ecal $r3 $r0 $r1 $r2 ; ecal id fd buf count
ecal $r3 $r0 $r1 $r2 ; ecal id fd buf count
ecal $r4 $r2 $r3 $r0 ; ecal id fd buf count
ecal $r3 $r0 $r1 $r2 ; ecal id fd buf count
> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/intrinsics/dbg --release --asm final | grep ecal

View file

@ -0,0 +1,4 @@
category = "run"
expected_result = { action = "return", value = 1 }
expected_result_new_encoding = { action = "return_data", value = "000000000000000B" }
expected_warnings = 10

View file

@ -0,0 +1,8 @@
[[package]]
name = "dbg_release"
source = "member"
dependencies = ["std"]
[[package]]
name = "std"
source = "path+from-root-A3D53D8266747D89"

View file

@ -0,0 +1,9 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
entry = "main.sw"
name = "dbg_release"
force-dbg-in-release = true
[dependencies]
std = { path = "../../../../../reduced_std_libs/sway-lib-std-core" }

View file

@ -0,0 +1,4 @@
cmds = [
"forc build --path {root} --asm final | grep ecal",
"forc build --path {root} --release --asm final | grep ecal"
]

View file

@ -0,0 +1,29 @@
script;
struct S {}
enum E { None: (), Some: S }
fn main() -> u64 {
// Primitive types
let _ = __dbg(8u8);
let _ = __dbg(16u16);
let _ = __dbg(32u32);
let _ = __dbg(64u64);
let _ = __dbg(0x100u256);
// strings
let _ = __dbg("Hello!");
let _ = __dbg(__to_str_array("Hello!"));
// Aggregates
let _ = __dbg((1u64, 2u64));
let _ = __dbg([1u64, 2u64]);
// Strucs and Enum
let _ = __dbg(S { });
let _ = __dbg(E::None);
let _ = __dbg(E::Some(S { }));
// should return its argument
__dbg(11u64)
}

View file

@ -0,0 +1,16 @@
---
source: test/src/snapshot/mod.rs
---
> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/intrinsics/dbg_release --asm final | grep ecal
ecal $r2 $r3 $r0 $r1 ; ecal id fd buf count
ecal $r3 $r0 $r1 $r2 ; ecal id fd buf count
ecal $r3 $r0 $r1 $r2 ; ecal id fd buf count
ecal $r3 $r0 $r1 $r2 ; ecal id fd buf count
ecal $r4 $r2 $r3 $r0 ; ecal id fd buf count
ecal $r3 $r0 $r1 $r2 ; ecal id fd buf count
> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/intrinsics/dbg_release --release --asm final | grep ecal
ecal $r2 $r6 $r0 $r1 ; ecal id fd buf count
ecal $r7 $r8 $r3 $r6 ; ecal id fd buf count
ecal $r3 $r4 $r2 $r0 ; ecal id fd buf count
ecal $r3 $r4 $r1 $r2 ; ecal id fd buf count

View file

@ -0,0 +1,4 @@
category = "run"
expected_result = { action = "return", value = 1 }
expected_result_new_encoding = { action = "return_data", value = "000000000000000B" }
expected_warnings = 10

View file

@ -236,6 +236,7 @@ pub(super) async fn run(
path.clone(),
PathBuf::from("/"),
build_target,
sway_core::DbgGeneration::Full,
);
// Include unit tests in the build.

View file

@ -13,7 +13,7 @@ const REDUCED_LIB_CONFIG_FILE_NAME: &str = "reduced_lib.config";
/// Creates the reduced versions of `std` libraries based on the list of
/// modules defined in [REDUCED_LIB_CONFIG_FILE_NAME] file for each reduced library
/// available in the [REDUCED_STD_LIBS_DIR_NAME].
pub(crate) fn create() -> Result<()> {
pub fn create() -> Result<()> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let reduced_libs_dir = format!("{manifest_dir}/src/e2e_vm_tests/{REDUCED_STD_LIBS_DIR_NAME}");
let std_lib_src_dir = format!("{manifest_dir}/../sway-lib-std/src");

View file

@ -77,40 +77,60 @@ pub(super) async fn run(filter_regex: Option<&regex::Regex>) -> Result<()> {
let _ = writeln!(&mut snapshot, "> {}", cmd);
// known commands
let cmd = if let Some(cmd) = cmd.strip_prefix("forc doc ") {
FORC_DOC_COMPILATION.call_once(|| {
compile_forc_doc();
});
format!("target/release/forc-doc {cmd} 1>&2")
} else if let Some(cmd) = cmd.strip_prefix("forc ") {
FORC_COMPILATION.call_once(|| {
compile_forc();
});
format!("target/release/forc {cmd} 1>&2")
} else {
panic!("Not supported. Possible commands: forc")
};
let mut last_output: Option<String> = None;
let o = duct::cmd!("bash", "-c", cmd.clone())
.dir(repo_root.clone())
.stderr_to_stdout()
.stdout_capture()
.env("COLUMNS", "10")
.unchecked()
.start()
.unwrap();
let o = o.wait().unwrap();
for cmd in cmd.split("|") {
let cmd = cmd.trim();
let _ = writeln!(
&mut snapshot,
"{}",
clean_output(&format!(
// known commands
let cmd = if let Some(cmd) = cmd.strip_prefix("forc doc ") {
FORC_DOC_COMPILATION.call_once(|| {
compile_forc_doc();
});
format!("target/release/forc-doc {cmd} 1>&2")
} else if let Some(cmd) = cmd.strip_prefix("forc ") {
FORC_COMPILATION.call_once(|| {
compile_forc();
});
format!("target/release/forc {cmd} 1>&2")
} else if let Some(cmd) = cmd.strip_prefix("grep ") {
let arg = cmd.trim();
if let Some(l) = last_output.take() {
let mut new_output = String::new();
for line in l.lines() {
if line.contains(arg) {
new_output.push_str(line);
new_output.push('\n');
}
}
last_output = Some(new_output);
}
continue;
} else {
panic!("Not supported. Possible commands: forc")
};
let o = duct::cmd!("bash", "-c", cmd.clone())
.dir(repo_root.clone())
.stderr_to_stdout()
.stdout_capture();
let o = if let Some(last_output) = last_output.as_ref() {
o.stdin_bytes(last_output.as_bytes())
} else {
o
};
let o = o.env("COLUMNS", "10").unchecked().start().unwrap();
let o = o.wait().unwrap();
last_output = Some(clean_output(&format!(
"exit status: {}\noutput:\n{}",
o.status.code().unwrap(),
std::str::from_utf8(&o.stdout).unwrap(),
))
);
)));
}
let _ = writeln!(&mut snapshot, "{}", last_output.unwrap_or_default());
}
fn stdout(root: &str, snapshot: &str) {