Skip to content

Commit

Permalink
Add Option and Result variants in prelude (FuelLabs#4504)
Browse files Browse the repository at this point in the history
## Description

This also handles ambiguities in cases where a lone ident is either a
variable or a variant; or a variable declaration or an enum scrutinee.

Fix FuelLabs#4480

## 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).
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
  • Loading branch information
IGI-111 authored Apr 28, 2023
1 parent 1ecc5e7 commit 6a3fba1
Show file tree
Hide file tree
Showing 75 changed files with 834 additions and 758 deletions.
4 changes: 2 additions & 2 deletions docs/book/src/advanced/generic_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,15 @@ Similar to Rust, Sway has what is colloquially known as the [turbofish](https://

```sway
fn foo<T, E>(t: T) -> Result<T, E> {
Result::Ok(t)
Ok(t)
}
```

In this code example, which is admittedly asinine, you can't possibly know what type `E` is. You'd need to provide the type manually, with a turbofish:

```sway
fn foo<T, E>(t: T) -> Result<T, E> {
Result::Ok::<T, MyErrorType>(t)
Ok::<T, MyErrorType>(t)
}
```

Expand Down
6 changes: 3 additions & 3 deletions docs/book/src/blockchain-development/access_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ To deliver an experience akin to the EVM's access control, the `std` library pro

The `msg_sender` function works as follows:

- If the caller is a contract, then `Result::Ok(Sender)` is returned with the `ContractId` sender variant.
- If the caller is external (i.e. from a script), then all coin input owners in the transaction are checked. If all owners are the same, then `Result::Ok(Sender)` is returned with the `Address` sender variant.
- If the caller is external and coin input owners are different, then the caller cannot be determined and a `Result::Err(AuthError)` is returned.
- If the caller is a contract, then `Ok(Sender)` is returned with the `ContractId` sender variant.
- If the caller is external (i.e. from a script), then all coin input owners in the transaction are checked. If all owners are the same, then `Ok(Sender)` is returned with the `Address` sender variant.
- If the caller is external and coin input owners are different, then the caller cannot be determined and a `Err(AuthError)` is returned.

## Contract Ownership

Expand Down
2 changes: 1 addition & 1 deletion docs/book/src/common-collections/storage_map.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ We can get a value out of the storage map by providing its `key` to the `get` me
{{#include ../../../../examples/storage_map/src/main.sw:storage_map_get}}
```

Here, `value1` will have the value that's associated with the first address, and the result will be `42`. The `get` method returns an `Option<V>`; if there’s no value for that key in the storage map, `get` will return `Option::None`. This program handles the `Option` by calling `unwrap_or` to set `value1` to zero if `map` doesn't have an entry for the key.
Here, `value1` will have the value that's associated with the first address, and the result will be `42`. The `get` method returns an `Option<V>`; if there’s no value for that key in the storage map, `get` will return `None`. This program handles the `Option` by calling `unwrap_or` to set `value1` to zero if `map` doesn't have an entry for the key.

## Storage Maps with Multiple Keys

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ contract;

// ANCHOR: identity
storage {
owner: Option<Identity> = Option::None,
owner: Option<Identity> = None,
}

// ANCHOR_END: identity
Expand Down
8 changes: 6 additions & 2 deletions docs/reference/src/documentation/misc/prelude.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ The prelude contains the following:
- [`Identity`](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/identity.sw): An enum containing `Address` & `ContractID` structs
- [`Vec`](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/vec.sw): A growable, heap-allocated vector
- [`StorageMap`](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/storage.sw): A key-value mapping in contract storage
- [`Option`](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/option.sw): An enum containing either some generic value `<T>` or an absence of that value
- [`Result`](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/result.sw): An enum used to represent either a success or failure of an operation
- [`Option`](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/option.sw): An enum containing either some generic value `<T>` or an absence of that value, we also expose the variants directly:
- `Some`
- `None`
- [`Result`](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/result.sw): An enum used to represent either a success or failure of an operation, we also expose the variants directly:
- `Ok`
- `Err`
- [`assert`](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/assert.sw): A module containing
- `assert`: A function that reverts the VM if the condition provided to it is false
- `assert_eq`: A function that reverts the VM and logs its two inputs v1 and v2 if the condition v1 == v2 is false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ There are two `storage` variables: `balance` & `user`. `balance` takes a single

## Reading from Storage

Retrieving data from a storage variable is done through the `.get(key)` method. That is to say that we state which storage variable we would like to read from and append `.get()` to the end while providing the key for the data that we want to retrieve. The method `get` returns an `Option`; if there is no value for `key` in the map, `get` will return `Option::None`.
Retrieving data from a storage variable is done through the `.get(key)` method. That is to say that we state which storage variable we would like to read from and append `.get()` to the end while providing the key for the data that we want to retrieve. The method `get` returns an `Option`; if there is no value for `key` in the map, `get` will return `None`.

In this example we wrap the [`Identity`](../../namespace/identity.md) of the caller with their provided `id` into a [tuple](../../../language/built-ins/tuples.md) and use that as the key.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ In this example we retrieve some `u64` at the position of `key`.
{{#include ../../../../code/operations/storage/store_get/src/main.sw:get}}
```

The function `get` returns an `Option`; if the storage slots requested have not been set before, `get` will return `Option::None`.
The function `get` returns an `Option`; if the storage slots requested have not been set before, `get` will return `None`.
8 changes: 4 additions & 4 deletions examples/option/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ script;

fn divide(numerator: u64, denominator: u64) -> Option<u64> {
if denominator == 0 {
Option::None
None
} else {
Option::Some(numerator / denominator)
Some(numerator / denominator)
}
}

Expand All @@ -13,8 +13,8 @@ fn main() {
// Pattern match to retrieve the value
match result {
// The division was valid
Option::Some(x) => std::logging::log(x),
Some(x) => std::logging::log(x),
// The division was invalid
Option::None => std::logging::log("Cannot divide by 0"),
None => std::logging::log("Cannot divide by 0"),
}
}
6 changes: 3 additions & 3 deletions examples/ownership/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ abi OwnershipExample {
}

storage {
owner: Option<Identity> = Option::None,
owner: Option<Identity> = None,
}

impl OwnershipExample for Contract {
// ANCHOR: revoke_owner_example
#[storage(write)]
fn revoke_ownership() {
storage.owner.write(Option::None);
storage.owner.write(None);
}
// ANCHOR_END: revoke_owner_example
// ANCHOR: set_owner_example
#[storage(write)]
fn set_owner(identity: Identity) {
storage.owner.write(Option::Some(identity));
storage.owner.write(Some(identity));
}
// ANCHOR_END: set_owner_example
#[storage(read)]
Expand Down
8 changes: 4 additions & 4 deletions examples/result/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ enum MyContractError {

fn divide(numerator: u64, denominator: u64) -> Result<u64, MyContractError> {
if (denominator == 0) {
return Result::Err(MyContractError::DivisionByZero);
return Err(MyContractError::DivisionByZero);
} else {
Result::Ok(numerator / denominator)
Ok(numerator / denominator)
}
}

fn main() -> Result<u64, str[4]> {
let result = divide(20, 2);
match result {
Result::Ok(value) => Result::Ok(value),
Result::Err(MyContractError::DivisionByZero) => Result::Err("Fail"),
Ok(value) => Ok(value),
Err(MyContractError::DivisionByZero) => Err("Fail"),
}
}
2 changes: 1 addition & 1 deletion examples/signatures/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn main() {

// A recovered Fuel address.
let result_address: Result<Address, EcRecoverError> = ec_recover_address(signature, MSG_HASH);
if let Result::Ok(address) = result_address {
if let Ok(address) = result_address {
log(address.value);
} else {
revert(0);
Expand Down
4 changes: 2 additions & 2 deletions examples/storage_vec/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ impl StorageVecContract for Contract {
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Option::Some(third) => log(third.read()),
Option::None => revert(42),
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
Expand Down
4 changes: 2 additions & 2 deletions examples/vec/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ fn main() {
// ANCHOR: vec_get
let third = v.get(2);
match third {
Option::Some(third) => log(third),
Option::None => revert(42),
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
Expand Down
3 changes: 3 additions & 0 deletions sway-ast/src/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum Pattern {
Wildcard {
underscore_token: UnderscoreToken,
},
/// A pattern made of a single ident, which could either be a variable or an enum variant
AmbiguousSingleIdent(Ident),
Var {
reference: Option<RefToken>,
mutable: Option<MutToken>,
Expand Down Expand Up @@ -51,6 +53,7 @@ impl Spanned for Pattern {
(None, Some(mut_token)) => Span::join(mut_token.span(), name.span()),
(None, None) => name.span(),
},
Pattern::AmbiguousSingleIdent(ident) => ident.span(),
Pattern::Literal(literal) => literal.span(),
Pattern::Constant(path_expr) => path_expr.span(),
Pattern::Constructor { path, args } => Span::join(path.span(), args.span()),
Expand Down
4 changes: 3 additions & 1 deletion sway-core/src/language/parsed/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,12 @@ pub enum ExpressionKind {
Error(Box<[Span]>),
Literal(Literal),
/// An ambiguous path where we don't know until type checking whether this
/// is a free function call or a UFCS (Rust term) style associated function call.
/// is a free function call, an enum variant or a UFCS (Rust term) style associated function call.
AmbiguousPathExpression(Box<AmbiguousPathExpression>),
FunctionApplication(Box<FunctionApplicationExpression>),
LazyOperator(LazyOperatorExpression),
/// And ambiguous single ident which could either be a variable or an enum variant
AmbiguousVariableExpression(Ident),
Variable(Ident),
Tuple(Vec<Expression>),
TupleIndex(TupleIndexExpression),
Expand Down
3 changes: 3 additions & 0 deletions sway-core/src/language/parsed/expression/scrutinee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub enum Scrutinee {
name: Ident,
span: Span,
},
AmbiguousSingleIdent(Ident),
StructScrutinee {
struct_name: CallPath,
fields: Vec<StructScrutineeField>,
Expand Down Expand Up @@ -66,6 +67,7 @@ impl Spanned for Scrutinee {
Scrutinee::CatchAll { span } => span.clone(),
Scrutinee::Literal { span, .. } => span.clone(),
Scrutinee::Variable { span, .. } => span.clone(),
Scrutinee::AmbiguousSingleIdent(ident) => ident.span(),
Scrutinee::StructScrutinee { span, .. } => span.clone(),
Scrutinee::EnumScrutinee { span, .. } => span.clone(),
Scrutinee::Tuple { span, .. } => span.clone(),
Expand Down Expand Up @@ -168,6 +170,7 @@ impl Scrutinee {
.collect::<Vec<TypeInfo>>(),
Scrutinee::Literal { .. }
| Scrutinee::CatchAll { .. }
| Scrutinee::AmbiguousSingleIdent(..)
| Scrutinee::Variable { .. }
| Scrutinee::Error { .. } => {
vec![]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,27 @@ impl ty::TyScrutinee {
value,
span,
} => type_check_enum(ctx, call_path, *value, span),
Scrutinee::AmbiguousSingleIdent(ident) => {
let maybe_enum = type_check_enum(
ctx.by_ref(),
CallPath {
prefixes: vec![],
suffix: ident.clone(),
is_absolute: false,
},
Scrutinee::Tuple {
elems: vec![],
span: ident.span(),
},
ident.span(),
);

if maybe_enum.is_ok() {
maybe_enum
} else {
type_check_variable(ctx, ident.clone(), ident.span())
}
}
Scrutinee::Tuple { elems, span } => type_check_tuple(ctx, elems, span),
Scrutinee::Error { .. } => err(vec![], vec![]),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,30 @@ impl ty::TyExpression {
// We've already emitted an error for the `::Error` case.
ExpressionKind::Error(_) => ok(ty::TyExpression::error(span, engines), vec![], vec![]),
ExpressionKind::Literal(lit) => Self::type_check_literal(engines, lit, span),
ExpressionKind::AmbiguousVariableExpression(name) => {
let call_path = CallPath {
prefixes: vec![],
suffix: name.clone(),
is_absolute: false,
};
if matches!(
ctx.namespace.resolve_call_path(&call_path).value,
Some(ty::TyDecl::EnumVariantDecl { .. })
) {
Self::type_check_delineated_path(
ctx.by_ref(),
TypeBinding {
span: call_path.span(),
inner: call_path,
type_arguments: TypeArgs::Regular(vec![]),
},
span,
None,
)
} else {
Self::type_check_variable_expression(ctx.by_ref(), name, span)
}
}
ExpressionKind::Variable(name) => {
Self::type_check_variable_expression(ctx.by_ref(), name, span)
}
Expand Down
3 changes: 3 additions & 0 deletions sway-core/src/semantic_analysis/node_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,9 @@ impl Dependencies {
// ordered
self.gather_from_call_path(&(name.clone()).into(), false, false)
}
ExpressionKind::AmbiguousVariableExpression(name) => {
self.gather_from_call_path(&(name.clone()).into(), false, false)
}
ExpressionKind::FunctionApplication(function_application_expression) => {
let FunctionApplicationExpression {
call_path_binding,
Expand Down
14 changes: 11 additions & 3 deletions sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2366,6 +2366,7 @@ fn fn_arg_to_function_parameter(
mutable,
name,
} => (reference, mutable, name),
Pattern::AmbiguousSingleIdent(ident) => (None, None, ident),
Pattern::Literal(..) => {
let error = ConvertParseTreeError::LiteralPatternsNotSupportedHere { span: pat_span };
return Err(handler.emit_err(error.into()));
Expand Down Expand Up @@ -2541,10 +2542,15 @@ fn path_expr_to_expression(
path_expr: PathExpr,
) -> Result<Expression, ErrorEmitted> {
let span = path_expr.span();
let expression = if path_expr.root_opt.is_none() && path_expr.suffix.is_empty() {
let expression = if path_expr.root_opt.is_none()
&& path_expr.suffix.is_empty()
&& path_expr.prefix.generics_opt.is_none()
{
// only `foo`, it coult either be a variable or an enum variant

let name = path_expr_segment_to_ident(context, handler, &path_expr.prefix)?;
Expression {
kind: ExpressionKind::Variable(name),
kind: ExpressionKind::AmbiguousVariableExpression(name),
span,
}
} else {
Expand Down Expand Up @@ -3076,14 +3082,15 @@ fn statement_let_to_ast_nodes(
span: Span,
) -> Result<Vec<AstNode>, ErrorEmitted> {
let ast_nodes = match pattern {
Pattern::Wildcard { .. } | Pattern::Var { .. } => {
Pattern::Wildcard { .. } | Pattern::Var { .. } | Pattern::AmbiguousSingleIdent(..) => {
let (reference, mutable, name) = match pattern {
Pattern::Var {
reference,
mutable,
name,
} => (reference, mutable, name),
Pattern::Wildcard { .. } => (None, None, Ident::new_no_span("_".into())),
Pattern::AmbiguousSingleIdent(ident) => (None, None, ident),
_ => unreachable!(),
};
if reference.is_some() {
Expand Down Expand Up @@ -3474,6 +3481,7 @@ fn pattern_to_scrutinee(
}
Scrutinee::Variable { name, span }
}
Pattern::AmbiguousSingleIdent(ident) => Scrutinee::AmbiguousSingleIdent(ident),
Pattern::Literal(literal) => Scrutinee::Literal {
value: literal_to_literal(context, handler, literal)?,
span,
Expand Down
Loading

0 comments on commit 6a3fba1

Please sign in to comment.