Skip to content

Commit 1270bfa

Browse files
authored
Marker traits (#6871)
## Description This PR introduces the concept of marker traits to the language. It is the first step towards implementing the [ABI errors RFC](https://github.com/FuelLabs/sway-rfcs/blob/master/rfcs/0014-abi-errors.md). Marker traits are traits automatically generated by the compiler. They represent certain properties of types and cannot be explicitly implemented by developers. The PR implements a common infrastructure for generating and type-checking marker traits as well as two concrete marker traits: - `Error`: represents a type whose instances can be used as arguments for the `panic` expression. (The `panic` expression is yet to be implemented.) - `Enum`: represents an enum type. Combining these two marker traits in trait constraints allow expressing constraints such is, e.g., "the error type must be an error enum": ``` fn panic_with_error<E>(err: E) where E: Error + Enum { panic err; } ``` Note that the generic name `Enum` is sometimes used in our tests to represent a dummy enum. In tests, it is almost always defined locally, and sometimes explicitly imported, so it will never clash with the `Enum` marker trait. A single test in which the clash occurred was easily adapted by explicitly importing the dummy `Enum`. The PR is the first step in implementing #6765. ## 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. - [ ] 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 8696e2c commit 1270bfa

File tree

47 files changed

+1181
-325
lines changed

Some content is hidden

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

47 files changed

+1181
-325
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,10 @@ jobs:
475475
run: cargo run --locked --release -p forc -- build --experimental storage_domains --release --locked --path ./test/src/sdk-harness
476476
- name: Cargo Test sway-lib-std - Experimental Feature 'storage_domains'
477477
run: cargo test --locked --release --manifest-path ./test/src/sdk-harness/Cargo.toml -- --nocapture
478+
- name: Build All Tests - Experimental Feature 'error_type'
479+
run: cargo run --locked --release -p forc -- build --experimental error_type --release --locked --path ./test/src/sdk-harness
480+
- name: Cargo Test sway-lib-std - Experimental Feature 'error_type'
481+
run: cargo test --locked --release --manifest-path ./test/src/sdk-harness/Cargo.toml -- --nocapture
478482

479483
forc-run-benchmarks:
480484
runs-on: buildjet-4vcpu-ubuntu-2204
@@ -541,14 +545,20 @@ jobs:
541545
run: forc build --path sway-lib-core && forc test --path sway-lib-core
542546
- name: Run Core Unit Tests - Experimental feature 'storage_domains'
543547
run: forc build --experimental storage_domains --path sway-lib-core && forc test --experimental storage_domains --path sway-lib-core
548+
- name: Run Core Unit Tests - Experimental feature 'error_type'
549+
run: forc build --experimental error_type --path sway-lib-core && forc test --experimental error_type --path sway-lib-core
544550
- name: Run Std Unit Tests
545551
run: forc build --path sway-lib-std && forc test --path sway-lib-std
546552
- name: Run Std Unit Tests - Experimental feature 'storage_domains'
547553
run: forc build --experimental storage_domains --path sway-lib-std && forc test --experimental storage_domains --path sway-lib-std
554+
- name: Run Std Unit Tests - Experimental feature 'error_type'
555+
run: forc build --experimental error_type --path sway-lib-std && forc test --experimental error_type --path sway-lib-std
548556
- name: Run In Language Unit Tests
549557
run: forc build --path test/src/in_language_tests && forc test --path test/src/in_language_tests
550558
- name: Run In Language Unit Tests - Experimental feature 'storage_domains'
551559
run: forc build --experimental storage_domains --path test/src/in_language_tests && forc test --experimental storage_domains --path test/src/in_language_tests
560+
- name: Run In Language Unit Tests - Experimental feature 'error_type'
561+
run: forc build --experimental error_type --path test/src/in_language_tests && forc test --experimental error_type --path test/src/in_language_tests
552562

553563
forc-pkg-fuels-deps-check:
554564
runs-on: buildjet-4vcpu-ubuntu-2204

docs/book/src/advanced/traits.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,43 @@ trait MyTrait {
134134

135135
Check the `associated types` section on [associated types](./associated_types.md) page.
136136

137+
## Trait Constraints
138+
139+
When writing generic code, you can constraint the choice of types for a generic argument by using the `where` keyword. The `where` keyword specifies which traits the concrete generic parameter must implement. In the below example, the function `expects_some_trait` can be called only if the parameter `t` is of a type that has `SomeTrait` implemented. To call the `expects_both_traits`, parameter `t` must be of a type that implements _both_ `SomeTrait` and `SomeOtherTrait`.
140+
141+
```sway
142+
trait SomeTrait { }
143+
trait SomeOtherTrait { }
144+
145+
fn expects_some_trait<T>(t: T) where T: SomeTrait {
146+
// ...
147+
}
148+
149+
fn expects_some_other_trait<T>(t: T) where T: SomeOtherTrait {
150+
// ...
151+
}
152+
153+
fn expects_both_traits<T>(t: T) where T: SomeTrait + SomeOtherTrait {
154+
// ...
155+
}
156+
```
157+
158+
## Marker Traits
159+
160+
Sway types can be classified in various ways according to their intrinsic properties. These classifications are represented as marker traits. Marker traits are implemented by the compiler and cannot be explicitly implemented in code.
161+
162+
E.g., all types whose instances can be used in the `panic` expression automatically implement the `Error` marker trait. We can use that trait, e.g., to specify that a generic argument must be compatible with the `panic` expression:
163+
164+
```sway
165+
fn panic_with_error<E>(err: E) where E: Error {
166+
panic err;
167+
}
168+
```
169+
170+
> **Note** `panic` expression and error types [have not yet been implemented](https://github.com/FuelLabs/sway/issues/6765)
171+
172+
All marker traits are defined in the `core::marker` module.
173+
137174
## Use Cases
138175

139176
### Custom Types (structs, enums)
@@ -160,8 +197,6 @@ fn play_game_with_deck<T>(a: Vec<T>) where T: Card {
160197
}
161198
```
162199

163-
> **Note** Trait constraints (i.e. using the `where` keyword) [have not yet been implemented](https://github.com/FuelLabs/sway/issues/970)
164-
165200
Now, if you want to use the function `play_game_with_deck` with your struct, you must implement `Card` for your struct. Note that the following code example assumes a dependency _games_ has been included in the `Forc.toml` file.
166201

167202
```sway

sway-core/src/language/call_path.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
OrdWithEnginesContext, PartialEqWithEngines, PartialEqWithEnginesContext,
55
},
66
parsed::QualifiedPathType,
7-
Engines, Ident, Namespace,
7+
Engines, Ident, Namespace, TypeArgument,
88
};
99
use serde::{Deserialize, Serialize};
1010
use std::{
@@ -284,20 +284,26 @@ impl<T: Spanned> Spanned for CallPath<T> {
284284
if self.prefixes.is_empty() {
285285
self.suffix.span()
286286
} else {
287+
let suffix_span = self.suffix.span();
287288
let mut prefixes_spans = self
288289
.prefixes
289290
.iter()
290291
.map(|x| x.span())
291-
//LOC below should be removed when #21 goes in
292+
// Depending on how the call path is constructed, we
293+
// might have a situation that the parts do not belong
294+
// to the same source and do not have the same source id.
295+
// In that case, we will take only the suffix' span, as
296+
// the span for the whole call path. Otherwise, we join
297+
// the spans of all the parts.
292298
.filter(|x| {
293-
Arc::ptr_eq(x.src(), self.suffix.span().src())
294-
&& x.source_id() == self.suffix.span().source_id()
299+
Arc::ptr_eq(x.src(), suffix_span.src())
300+
&& x.source_id() == suffix_span.source_id()
295301
})
296302
.peekable();
297303
if prefixes_spans.peek().is_some() {
298-
Span::join(Span::join_all(prefixes_spans), &self.suffix.span())
304+
Span::join(Span::join_all(prefixes_spans), &suffix_span)
299305
} else {
300-
self.suffix.span()
306+
suffix_span
301307
}
302308
}
303309
}
@@ -391,6 +397,33 @@ impl CallPath {
391397
}
392398
converted
393399
}
400+
401+
/// Create a string form of the given [CallPath] and zero or more [TypeArgument]s.
402+
/// The returned string is convenient for displaying full names, including generic arguments, in help messages.
403+
/// E.g.:
404+
/// - `some::module::SomeType`
405+
/// - `some::module::SomeGenericType<T, u64>`
406+
///
407+
/// Note that the trailing arguments are never separated by `::` from the suffix.
408+
pub(crate) fn to_string_with_args(&self, engines: &Engines, args: &[TypeArgument]) -> String {
409+
let args = args
410+
.iter()
411+
.map(|type_arg| engines.help_out(type_arg).to_string())
412+
.collect::<Vec<_>>()
413+
.join(", ");
414+
415+
format!(
416+
"{}{}",
417+
// TODO: Replace with a context aware string representation of the path
418+
// once https://github.com/FuelLabs/sway/issues/6873 is fixed.
419+
&self,
420+
if args.is_empty() {
421+
String::new()
422+
} else {
423+
format!("<{args}>")
424+
}
425+
)
426+
}
394427
}
395428

396429
impl<T: Clone> CallPath<T> {

sway-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod debug_generation;
1717
pub mod decl_engine;
1818
pub mod ir_generation;
1919
pub mod language;
20+
pub mod marker_traits;
2021
mod metadata;
2122
pub mod query_engine;
2223
pub mod semantic_analysis;

sway-core/src/marker_traits.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use sway_types::{Ident, SourceEngine};
2+
3+
use crate::{
4+
language::{parsed::ImplSelfOrTrait, ty::TyTraitDecl, CallPathType},
5+
namespace::Module,
6+
};
7+
8+
impl TyTraitDecl {
9+
pub(crate) fn is_marker_trait(&self) -> bool {
10+
assert!(
11+
matches!(self.call_path.callpath_type, CallPathType::Full),
12+
"call paths of trait declarations must always be full paths"
13+
);
14+
15+
is_core_marker_module_path(&self.call_path.prefixes)
16+
}
17+
}
18+
19+
impl Module {
20+
pub(crate) fn is_core_marker_module(&self) -> bool {
21+
is_core_marker_module_path(self.mod_path())
22+
}
23+
}
24+
25+
impl ImplSelfOrTrait {
26+
pub(crate) fn is_autogenerated(&self, source_engine: &SourceEngine) -> bool {
27+
source_engine
28+
.is_span_in_autogenerated(&self.block_span)
29+
.unwrap_or(false)
30+
}
31+
}
32+
33+
fn is_core_marker_module_path(path: &[Ident]) -> bool {
34+
path.len() == 2 && path[0].as_str() == "core" && path[1].as_str() == "marker"
35+
}

0 commit comments

Comments
 (0)